如何在 C 和 ubuntu 中用盐加密 SHA512 散列?

How can I encrypt SHA512 hash with salt in C and ubuntu?

我正在尝试用盐加密散列。

在命令行中,我使用以下命令在影子文件中找到了正确的哈希值。

openssl passwd -6 -salt abcd apple $6$abcd$vIWAp1OzuGuo376cRkZ5DXcgI8KIlnUibsk.iydtfCFqb9okOz.S70Ysu7.qRRg9Me5XwAyTjkZBQJXiIxwpL/

我尝试使用 openssl(How to convert a raw 64-byte binary to Hex or ASCII in C) 在 C 中获取它。

#include <openssl/sha.h>
#include <stdio.h>
#include <string.h>

int main() {
        char data[] = "abcdapple";
        unsigned char hash[SHA512_DIGEST_LENGTH*2+1];
        SHA512((unsigned char *)&data, strlen(data), (unsigned char *)&hash);

        char buffer[SHA512_DIGEST_LENGTH*2+1];
        for(int i =0; i < SHA512_DIGEST_LENGTH; ++i)
                sprintf(&buffer[i*2],"%02x", (unsigned int)hash[i]);
        printf("digest: %s\n", buffer);
        printf("\n");
}

但生成的哈希是 ea1d5a8b11297d20f954a3ab15092d21b733484b2eb9b7226b2b138639f0df30627774945458a774eb279cd83d2e977a2bc5599606d6a9a3b2f1875f9d=3

我觉得我没用好盐。似乎还有一个基本问题。 我不能用C实现加salt的加密程序吗?

您可以在 GitHub 找到 openssl passwd 命令(尤其是核心 do_passwd 函数)的源代码:https://github.com/openssl/openssl/blob/8ea761bf40e6578ecd95ec47772ef86a2e4d4607/apps/passwd.c#L795-L874

(非常 non-trivial,290 行)SHA-512 密码哈希计算函数位于 https://github.com/openssl/openssl/blob/8ea761bf40e6578ecd95ec47772ef86a2e4d4607/apps/passwd.c#L509-L793

之上一点

您最好只从您的应用中调用 openssl passwd 作为子进程。

您尝试生成的不是普通的 SHA-512 哈希。这是一个 散列密码,使用 special-purpose 算法 基于 SHA-512。 openssl passwd 计算此算法,但 OpenSSL 库的 SHA512 函数计算普通 SHA-512。 special-purpose 算法和其他几个具有相同功能的算法记录在 crypt(5) manpage.

您可以可能使用crypt_r function, from the libcrypt库(由于历史原因,该库的命名不准确;它提供password-hashing 算法),使用与 openssl passwd 相同的 special-purpose 算法计算散列密码。在我的电脑上,这个程序打印出与你得到的以 $abcd 开头的相同字符串:

#include <crypt.h>
#include <string.h>
#include <stdio.h>

int main(void)
{
    struct crypt_data cd;
    memset(&cd, 0, sizeof cd);
    puts(crypt_r("appple", "$abcd", &cd));
    return 0;
}

注意第二个参数(在我一直链接到的文档中称为“设置字符串”)如何包含盐值 abcd 和前缀 $(告诉crypt_r 使用基于 SHA-512 的哈希)。它以这种方式工作,因此 login(1) 可以调用 crypt_r,并将刚刚从 tty 读取的密码作为第一个参数,从影子文件中输入的密码作为第二个参数,如果它返回与第二个参数相同的字符串表示用户已成功通过身份验证。

像这样编译和运行:

$ gcc -std=gnu11 -O test.c -lcrypt
$ test $(./a.out) = '$abcd$vIWAp1OzuGuo376cRkZ5DXcgI8KIlnUibsk.iydtfCFqb9okOz.S70Ysu7.qRRg9Me5XwAyTjkZBQJXiIxwpL/'; echo $?
0

我说 可能 因为这个库支持的 special-purpose 散列算法集因 Unix 而异。如果(且仅当)您可以将字符串 $abcd$... 放入 /etc/shadow 中某个帐户的密码条目中,然后使用密码 appple 以该帐户成功登录,那么它应该可以工作。

libcrypt 也可能有一个名为 crypt_gensalt_rn 的函数,您可以使用它来生成设置字符串和 select 适当的哈希算法。这是该功能的演示:

#include <crypt.h>
#include <string.h>
#include <stdio.h>

int main(void)
{
    char setting[CRYPT_GENSALT_OUTPUT_SIZE];
    crypt_gensalt_rn(0, 0, 0, 0, setting, CRYPT_GENSALT_OUTPUT_SIZE);

    struct crypt_data cd;
    memset(&cd, 0, sizeof cd);
    puts(crypt_r("appple", setting, &cd));
    return 0;
}

如果你编译并运行这个程序它将不会打印以$abcd开头的字符串;它将打印其他内容,例如

$y$j9T[=13=]aVoGQ/PFN0PHbcYlKZdZ1NbMJLbRliM7fmtSAeZoy3OoRsBqETpAZXQpnPey82

每次你 运行 它打印的内容都会改变,但它输出的每个字符串都可以用作 /etc/shadow 条目,允许某人使用密码登录 appple.您可以使用我保留为 0 的前四个参数来控制它的行为。

(披露:我是 lib(x)crypt 的作者之一。)