生成可以在不保留白名单的情况下进行验证的一组唯一密钥

时间:2018-11-16 13:29:31

标签: c hash key crc

我需要生成10个字节的唯一ID集。这些集可能很大(即10000个值),并由有限内存设备检查其有效性。因此,有人在设备中输入了其中一个ID,该设备应该能够辨别该ID是否是真实的(由我生成)。
基本方法是将相同的ID集存储在设备的内存中,并对照该列表进行检查,但是我不能使用所有的内存。
我认为的第二种方法是使用CRC或哈希函数:例如,启用所有CRC为X的ID。这里的问题是我应该遍历所有可能的ID组合,以找到给出正确CRC的ID。
理想情况下,我想找到一个/两个像这样工作的函数:

 uint8_t * generate_ID(uint16_t index);
 bool is_valid validate_key(uint8_t * ID);
 //optional
 uint16_t index find_index(uint8_t * ID);

 //example
 //generate id value from index 0
 uint8_t ID[10] = generate_ID(0)
 //id is now {0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b}

 bool is_valid = validate_key(ID);
 //is_valid is True
 uint16_t index = find_index(ID);
 //index is now 0
 ID[0] = 0xff; //change ID with random value
 is_valid = validate_key(ID);
 //is_valid is now False

 //BONUS: use also a "seed" value, so that I can differentiate through sets of ids:
 uint8_t * generate_ID(uint16_t index, uint16_t seed);
 bool is_valid validate_key(uint8_t * ID, uint16_t seed);

find_index()是可选的,因为一旦我知道键是有效的,我就可以简单地遍历所有索引以找到匹配的索引。
基本上,generate_ID()函数应该足够复杂,以便在不知道可观的ID的情况下也不容易猜测,但可以在功能有限的嵌入式CPU(Cortex M0)上进行计算

2 个答案:

答案 0 :(得分:2)

10字节的密钥不足以确保任何安全。

您需要一个安全的哈希函数,例如SHA2-256,其输出长度为32字节。 SHA2可以在大多数系统上轻松实现。

您的密钥需要两个部分:

[text + hash]

第一部分就像一个“用户名”,第二部分就像一个“密码”

您还需要一个“秘密密钥”。该密钥是存储在您的软件中的字节数组。然后,将“秘密密钥”添加到“用户名”。查找结果字符串的SHA2哈希。现在,您得到的输出为原始文本的长度+哈希的32个字节。

您可以将此密钥用作唯一的可验证ID。

要测试密钥的真实性,请使用“用户名”部分并添加您的秘密密钥。取该字符串的SHA2,结果应匹配“ password”

如果保密性和唯一性不是大问题,那么可以使用输出为16个字节的MD5。将纯文本更改为二进制,以便可以在更少的字节中存储更多信息,并且您的最终键只有20个字节。您可以再减少一点,但不建议减少到10个字节。

这里是一个例子。我通过以下链接使用了SHA2实现:
https://github.com/B-Con/crypto-algorithms(我不确定它是否可以在大端机上运行)

任何SHA2实施都应该起作用。

void sha2(BYTE* dst, const BYTE* src, int len)
{
    SHA256_CTX ctx;
    sha256_init(&ctx);
    sha256_update(&ctx, (const BYTE*)src, len);
    sha256_final(&ctx, (BYTE*)dst);
}

void create_verifiable_id(const BYTE* source, BYTE *uid)
{
    BYTE hash[32];
    sha2(hash, source, ID_SIZE);

    //combine source + hash
    memcpy(uid, source, ID_SIZE);
    memcpy(uid + ID_SIZE, hash, 32);
}

int test_verfiable_id(const BYTE *uid)
{
    BYTE hash[32];
    sha2(hash, uid, ID_SIZE);

    //hash should match the second part of uid
    return memcmp(hash, uid + ID_SIZE, 32) == 0;
}

int main(void)
{
    //use a number from 0 to 0xFFFFFFFF, store in buf (4 bytes)
    //this is the "plain text" portion
    int number = 0x12345678;
    BYTE buf[ID_SIZE];
    for(int i = 0; i < sizeof(buf); i++)
    {
        buf[i] = number & 0xFF;
        number >>= 8;
    }

    //add sha2 to "plain text" to make verifiable id
    BYTE verifiable_id[32 + ID_SIZE];
    create_verifiable_id(buf, verifiable_id);

    printf("UID as hex string:\n");
    for(int i = 0; i < 32 + ID_SIZE; i++)
        printf("%02X", verifiable_id[i] & 0xFF);
    printf("\n");

    printf("Test (should succeed): %d\n", test_verfiable_id(verifiable_id));

    //change verifiable_id and test it again
    verifiable_id[0]++;
    printf("Test (should fail): %d\n", test_verfiable_id(verifiable_id));
    return 0;
}

答案 1 :(得分:0)

一种非常简单的方法是使用模块化乘法逆,如我在我的博客文章http://blog.mischel.com/2017/06/20/how-to-generate-random-looking-keys/中所述。

想法是将数字从1映射到某个数字x,以便每个数字都在同一范围内生成唯一值。因此,例如,映射可能是:

1 -> 9875
2 -> 362
3 -> 5247
...

这是可逆的计算。因此,如果f(1)=> 9875,则g(9875)=> 1。

在博客文章中显示的代码中,您可以通过修改xm的值来更改映射。

如果希望键是字母数字,则需要在生成整数后对其进行编码。然后,在用户输入该数字之后并尝试进行验证之前,您必须将它们解码回整数。

因此,为了进行验证,请将m设置为非常大的数字。适当设置x,最好设置一个大于m的质数。使用这些值生成前10,000个密钥。在应该验证这些数字的设备上,只需提供xm值以及最大索引(即10,000)。

因此,用户输入他们给定的密钥。您对密钥生成进行反向操作,得到一个介于1到10,000之间的数字。您知道该号码有效。如果您的反向计算返回的数字小于1或大于10,000,则该密钥无效。

您可以通过仅向每个设备提供它认为有效的开始和结束值来将其扩展到多个设备。不管如何,反向键计算都是相同的。

此技术可确保唯一性。安全...主要是因为晦涩难懂。如果有人知道正在使用的算法,包括xm值,并且知道设备将设置为接受的数字范围,那么他可以生成密钥来破坏系统。这是否是一个问题,只有您可以回答。有人试图打败您的系统会有什么风险,如果他们成功了会付出什么代价?