如何在C编程中使用SHA1哈希算法

31

我正在尝试编写一个C程序,证明SHA1几乎是不会发生碰撞的,但我无法弄清楚如何为我的输入值实际创建哈希。我只需要创建哈希,将十六进制值存储到数组中即可。经过一些谷歌搜索,我找到了OpenSSL文档,指导我使用以下内容:

 #include <openssl/sha.h>

 unsigned char *SHA1(const unsigned char *d, unsigned long n,
                  unsigned char *md);

 int SHA1_Init(SHA_CTX *c);
 int SHA1_Update(SHA_CTX *c, const void *data,
                  unsigned long len);
 int SHA1_Final(unsigned char *md, SHA_CTX *c);

我认为我应该使用unsigned char *SHA1或SHA1_Init,但是我不确定参数应该是什么,给定x是我的输入需要进行哈希处理。请问有人能够帮我澄清这个问题吗?谢谢。


你的输入值是什么:内存字符串还是文件内容? - Basile Starynkevitch
我正在编写一个生日攻击程序,每次清空数组时都应该创建一个新的哈希并将其添加到末尾。我只是想保持简单,对i的值进行哈希处理。快速回答,在内存字符串中。 - spassen
1
“证明SHA1几乎无碰撞”是什么意思?SHA1是一个160位的哈希值,因此有2^160个可能的值,但比2^160个可能的字符串(长度小于1MB)要多得多,因此会产生大量碰撞。如果您只想测试是否从大量随机生成的字符串中获得碰撞,则需要的字符串数量非常高(除非您早期发现了碰撞,但SHA1已经经过充分测试,可以将其分配给可忽略的小概率)。 - Daniel Fischer
我意识到可能会有很多可能的碰撞,但目标是证明找到一个碰撞需要相当长的时间(大约2^80),而要找到与特定哈希匹配的碰撞则需要更长的时间。 - spassen
2
但实际上,你无法测试超过2^34个字符串。即使SHA1以一种只需要2^50个字符串就能发生碰撞的方式被扭曲,你几乎肯定也看不到它。 - Daniel Fischer
显示剩余2条评论
7个回答

56
如果你已经拥有了所有的数据,只需要使用 SHA1 函数即可:
// The data to be hashed
char data[] = "Hello, world!";
size_t length = strlen(data);

unsigned char hash[SHA_DIGEST_LENGTH];
SHA1(data, length, hash);
// hash now contains the 20-byte SHA-1 hash
如果你只能逐个获取数据,并且希望在接收到数据时计算哈希值,那么请使用其他函数:
// Error checking omitted for expository purposes

// Object to hold the current state of the hash
SHA_CTX ctx;
SHA1_Init(&ctx);

// Hash each piece of data as it comes in:
SHA1_Update(&ctx, "Hello, ", 7);
...
SHA1_Update(&ctx, "world!", 6);
// etc.
...
// When you're done with the data, finalize it:
unsigned char hash[SHA_DIGEST_LENGTH];
SHA1_Final(hash, &ctx);

我尝试使用sha1函数,但是当我在终端中编译时,它会显示Undefined reference to SHA1。我没有收到任何其他方面的投诉。你有什么想法我缺少了什么吗? - spassen
13
您需要与 OpenSSL 运行时库进行链接。假设您正在使用 gcc,将“-lcrypto”添加到您的链接器命令行中即可。 - Adam Rosenfield
如何生成 HMACSHA1? - Cmag
不需要错误处理吗?对我来说,SHA1_Final会崩溃,但我不知道为什么。有没有办法打印错误信息? - BTR Naidu
如何获取 hash 值?我用 printf("<<sha1=%s>>\n", hash); 得到了�*���qE)DDF:� �4�。 - Kostiantyn
在第一个例子中,sizeof(data) 是14。在第二个例子中,组合大小为13。哈希值将不同。 - Nisse Engström

12

它们是实现同一目的的两种不同方式。

具体来说,你可以要么使用 SHA_Init,然后尽可能多次地使用 SHA_Update 传递你的数据,再使用 SHA_Final 获得摘要,或者你可以使用 SHA1

之所以有两种模式,是因为在对大文件进行哈希处理时,通常会将文件分块读取,因为另一种方法会使用大量内存。因此,随着处理过程的进行,跟踪 SHA_CTX - SHA上下文 - 允许你绕过这个问题。算法内部也符合这个模型 - 即,数据是按块传递的。

SHA 方法应该相当简单。另一种方法的工作方式如下:

unsigned char md[SHA_DIGEST_LENGTH];
SHA_CTX context;
int SHA1_Init(&context);

for ( i = 0; i < numblocks; i++ )
{
    int SHA1_Update(&context, pointer_to_data, data_length);
}
int SHA1_Final(md, &context);

关键是,在结尾处md将包含二进制摘要,而不是十六进制表示-它不是一个字符串,也不应该被用作字符串。


如何生成HMACSHA1? - Cmag
2
@Clustermagnet hmacsha1是一种HMAC算法,使用SHA1作为哈希。它的想法与我在这里的答案相同(请参见此处),但对于特定于HMAC的EVP_MD参数,您需要指定EVP_sha1() - user257111
1
@Cmag - 请参考 OpenSSL 维基上的 EVP Signing and Verifying | HMAC。此外,还可以在 Stack Overflow 上查看 Using HMAC vs EVP functions in OpenSSL - jww

3

Adam Rosenfield的答案是正确的,但要使用strlen而不是sizeof,否则哈希将包括空终止符。这在这种情况下可能没问题,但如果你需要将哈希与其他工具生成的哈希进行比较,则会出现问题。

// The data to be hashed
char data[] = "Hello, world!";
size_t length = strlen(data);

unsigned char hash[SHA_DIGEST_LENGTH];
SHA1(data, length, hash);
// hash now contains the 20-byte SHA-1 hash

3
我认为我应该使用 unsigned char *SHA1SHA1_Init ...

对于OpenSSL库的较新版本,如1.0.2和1.1.0,项目建议使用EVP接口。在OpenSSL维基上提供了使用SHA256的EVP消息摘要的示例: EVP Message Digests
#define handleErrors abort

EVP_MD_CTX *ctx;

if((ctx = EVP_MD_CTX_create()) == NULL)
    handleErrors();

if(1 != EVP_DigestInit_ex(ctx, EVP_sha256(), NULL))
    handleErrors();

unsigned char message[] = "abcd .... wxyz";
unsinged int message_len = sizeof(message);

if(1 != EVP_DigestUpdate(ctx, message, message_len))
    handleErrors();

unsigned char digest[EVP_MAX_MD_SIZE];
unsigned int digest_len = sizeof(digest);

if(1 != EVP_DigestFinal_ex(ctx, digest, &digest_len))
    handleErrors();

EVP_MD_CTX_destroy(ctx);

3
第一个函数(SHA1())是较高级的函数,可能是您想要的函数。文档对用法非常清晰-d是输入,n是其大小,md是结果存放的位置(您需要分配内存)。
至于另外三个函数-这些是较低级别的函数,我相信它们在内部被第一个函数使用。它们更适合需要以块为单位处理的较大输入。

2

像这样计算哈希值

// Object to hold the current state of the hash
SHA_CTX ctx;
SHA1_Init(&ctx);

// Hash each piece of data as it comes in:
SHA1_Update(&ctx, "Hello, ", 7);
...
SHA1_Update(&ctx, "world!", 6);
// etc.
...
// When you're done with the data, finalize it:
unsigned char tmphash[SHA_DIGEST_LENGTH];
SHA1_Final(tmphash, &ctx);

最后,您可以通过以下代码将哈希解码为易于理解的形式。
unsigned char hash[SHA_DIGEST_LENGTH*2];

int i = 0;
for (i=0; i < SHA_DIGEST_LENGTH; i++) {
    sprintf((char*)&(hash[i*2]), "%02x", tmphash[i]);
}
// And print to stdout
printf("Hash: %s\n", hash);

0

让代码说话

SQLite开发树包含一个工具{{link1:DBHASH}}的源代码,用于对整个数据库应用SHA1。完整包含SHA1实现。

你可能会发现研究这段代码是可行的。

附注:还有{{link2:作为SQLite用户定义函数实现的SHA1}}。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接