我正在try 使用Linux中的OpenSSL来验证由DS28C36返回的签名.出于测试目的,我将粘贴的公钥和签名复制到全局数组中.关于签名转换,我关注了这个老帖子:Creating a DER formatted ECDSA signature from raw r and s 然而,当我使用i2d_ECDSA_SIG转换它时,由于某些原因,它只返回2个字节,当然下面的ECDSA_Verify失败了.此外,当我try 使用BN_bn2hex转储它时,我得到了一个分段错误.我不确定我在这里做错了什么.

谢谢你的帮忙

这是我的测试代码

#include <errno.h>
#include <unistd.h>
#include <malloc.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <resolv.h>

#include <openssl/obj_mac.h>
#include <openssl/ec.h>
#include <openssl/ecdsa.h>
#include <openssl/crypto.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>

unsigned char rom_id[] = {0x4c, 0x51, 0xb7, 0x12, 0x00, 0x00, 0x00, 0xfa};

unsigned char man_id[] = {0x00, 0x80};

unsigned char pub_key_raw[] = { 0x04, 
0x34, 0xe3, 0x12, 0x1d, 0x73, 0xc4, 0x25, 0x68, 0xef, 0xf8, 0xcc, 0x89, 0xc7, 0x77, 0x9c, 0x4e,
0x16, 0xe7, 0x79, 0xd5, 0x76, 0x68, 0x0e, 0xff, 0xad, 0x1f, 0x53, 0xd6, 0x33, 0x70, 0xb8, 0xa3,
0xa5, 0xb9, 0x92, 0xc9, 0x3c, 0x75, 0xf7, 0x07, 0xbf, 0xed, 0xe1, 0x25, 0xbf, 0x1f, 0xcb, 0xeb,
0x30, 0x57, 0x4f, 0x05, 0x96, 0x28, 0x9f, 0x66, 0x33, 0x7a, 0x46, 0xb6, 0x76, 0x35, 0x8c, 0xdd};

unsigned char challenge[] = {0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc};

unsigned char usr_page3[] = {
                   0xB6, 0xB5, 0x88, 0x2C, 0xE1, 0xC3, 0x4F, 0x2B, 0x56, 0x36, 0xD7, 0xE3, 0x22, 0x5E, 0x3B, 0x34,
                   0x5D, 0x33, 0xD6, 0x7F, 0xEC, 0x1C, 0x2D, 0x45, 0x86, 0xFD, 0x14, 0xE6, 0x05, 0x39, 0x07, 0xF9};

unsigned char sig_s[] = {
               0x18, 0x4e, 0xbf, 0xa0, 0x9b, 0xe6, 0xeb, 0x66, 0x42, 0x1f, 0x01, 0x9e, 0xb8, 0xf5, 0xf5, 0x18,
               0x41, 0x34, 0x37, 0xd8, 0x20, 0x6e, 0x1d, 0x69, 0x94, 0x05, 0x15, 0x98, 0xa8, 0x64, 0x90, 0x1e};

unsigned char sig_r[] = {
               0x8c, 0xf0, 0x3e, 0xb2, 0x51, 0x87, 0xb2, 0x5c, 0x78, 0x98, 0x71, 0xd9, 0x57, 0x5b, 0xdf, 0x13,
               0x6e, 0x1c, 0xcd, 0x18, 0xba, 0xee, 0xec, 0x5e, 0xac, 0xbe, 0x95, 0xf1, 0x2f, 0x88, 0xaa, 0xa2};

void hexdump(void* buf, size_t length) {
    unsigned char* p = (unsigned char*)buf;
    unsigned char c;
    int i = 0;
    while (i < length) {
        printf("%04x: ", i);
        for (int j = 0; j < 16; j++) {
            if (i + j < length) {
                c = *(p + i + j);
                printf("%02x ", c);
            } else {
                printf("   ");
            }
        }
        printf(" ");
        for (int j = 0; j < 16; j++) {
            if (i + j < length) {
                c = *(p + i + j);
                if (c >= 32 && c <= 126) {
                    printf("%c", c);
                } else {
                    printf(".");
                }
            } else {
                printf(" ");
            }
        }
        printf("\n");
        i += 16;
    }
}

int main(int argc, char **argv)
{
   unsigned char *sig_ptr;
   char *s_r, *s_s;
   int param, msg_len, sig_len;
   unsigned char message[256];
   unsigned char hash[SHA256_DIGEST_LENGTH], signature_der[256];

   EC_GROUP *g;
   EC_POINT *p;
   EC_KEY  *k;
   SHA256_CTX sha256;
   ECDSA_SIG* ec_sig = ECDSA_SIG_new();

   OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS, NULL);
   OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL);

   // seed the random number generator
   srand((unsigned)time(NULL));

   //Some initialization works
   k = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
   EC_KEY_set_conv_form(k, POINT_CONVERSION_UNCOMPRESSED);
   g = EC_KEY_get0_group(k);
   p = EC_POINT_new(g);

   //Load EC points
   if (1 != EC_POINT_oct2point(g, p, pub_key_raw, 65, NULL)){
       printf("EC_POINT_oct2point failed:\n");
       printf("%s\n", ERR_error_string(ERR_get_error(), NULL));
   }

   //Set EC public key
   if (1 != EC_KEY_set_public_key(k, p)){
       printf("EC_KEY_set_public_key failed:\n");
       printf("%s\n", ERR_error_string(ERR_get_error(), NULL));
   }
   //Check EC public key
   if (1 != EC_KEY_check_key(k)) {
       printf("EC_KEY_check_key failed:\n");
       printf("%s\n", ERR_error_string(ERR_get_error(), NULL));
   }
   else {
       printf("Public key verified OK\n");
   }

   printf(" Page Data:   "); 
   for (int i = 0; i < 32; i++){
       printf("%02X", usr_page3[i]);
   }
   printf("\n");
   hexdump(usr_page3, 32);
   printf("\n Challenge:   "); 
   for (int i = 0; i < 8; i++){
    printf("%02X",challenge[i]);
   }
   printf("\n");
   hexdump(challenge, 8);


   // ROM NO
   msg_len = 0;
   memcpy(&message[msg_len],rom_id,8);
   msg_len += 8;
   // Page Data
   memcpy(&message[msg_len], usr_page3, 32);
   msg_len += 32;
   // Challenge (Buffer)
   memcpy(&message[msg_len], challenge, 32);
   msg_len += 32;
   // Page#
   message[msg_len++] = 3;//
   // MANID
   memcpy(&message[msg_len], man_id, 2);
   msg_len += 2;

   // Calculate Hash 
   SHA256_Init(&sha256);
   SHA256_Update(&sha256, message, strlen(message));
   SHA256_Final(hash, &sha256);

   if (NULL == BN_bin2bn((unsigned char*)sig_r, 32, ECDSA_SIG_get0_r(ec_sig))) {
       printf("BN_bin2bn for r failed:\n");
       printf("%s\n", ERR_error_string(ERR_get_error(), NULL));
   }
 
   if (NULL == BN_bin2bn((unsigned char*)sig_s, 32, ECDSA_SIG_get0_s(ec_sig))) {
       printf("BN_bin2bn for s failed:\n");
       printf("%s\n", ERR_error_string(ERR_get_error(), NULL));
   }

   s_r = BN_bn2hex(ECDSA_SIG_get0_r(ec_sig));
   s_s = BN_bn2hex(ECDSA_SIG_get0_s(ec_sig));
   printf("(sig->r, sig->s): (%s,%s)\n", s_r, s_s);

   sig_ptr = signature_der;
   sig_len = i2d_ECDSA_SIG(ec_sig, &sig_ptr);
   printf("converted signature length = %d\n", sig_len);

   // Validate ECDSA signature
   if (1 != ECDSA_verify(0, hash, sizeof(hash), signature_der, sig_len, k)) {
       printf("ECDSA_verify failed:\n");
       printf("%s\n", ERR_error_string(ERR_get_error(), NULL));
       printf("Signature:\n");
       hexdump(signature_der, sizeof(signature_der));
   }
}

推荐答案

初步来说,您的散列计算不正确.首先,您(try )从challenge复制32个字节,其中只包含8个字节.从技术上讲,这是未定义的行为,实现被允许做任何事情,包括销毁地球--但大多数不允许;在实践中,通常的结果要么崩溃,要么使用垃圾数据,由于您在这里没有观察到崩溃,您的实现很可能是后者.其次,您使用strlen(message)作为数据的长度,但事实并非如此,因为以上challenge%的垃圾可能包含空字节,如果不包含,man_id肯定会包含空字节.

然而,这只会导致签名验证失败,而不是您所询问的症状.对他们来说:

    ECDSA_SIG* ec_sig = ECDSA_SIG_new();
...
   if (NULL == BN_bin2bn((unsigned char*)sig_r, 32, ECDSA_SIG_get0_r(ec_sig))) {
       printf("BN_bin2bn for r failed:\n");
       printf("%s\n", ERR_error_string(ERR_get_error(), NULL));
   }

这将创建一个新的ECDSA_SIG对象,但没有实际的签名,因此它的R和S字段为空(NULL).当您调用_get0_r时,它只返回字段而不判断,BN_bin2bn(Buffer,len,NULL)creates a new BN object包含它返回的数字(作为指针),但您立即丢弃它.简而言之,这不会将R值放在ec_sig中.

   if (NULL == BN_bin2bn((unsigned char*)sig_s, 32, ECDSA_SIG_get0_s(ec_sig))) {
       printf("BN_bin2bn for s failed:\n");
       printf("%s\n", ERR_error_string(ERR_get_error(), NULL));
   }

S也是如此.

   s_r = BN_bn2hex(ECDSA_SIG_get0_r(ec_sig));
   s_s = BN_bn2hex(ECDSA_SIG_get0_s(ec_sig));
   printf("(sig->r, sig->s): (%s,%s)\n", s_r, s_s);

如果这样做,它会try 将ec_sig中仍然存在的空指针视为BN对象并打印它们,但由于空指针不是实际对象,它会崩溃.

   sig_ptr = signature_der;
   sig_len = i2d_ECDSA_SIG(ec_sig, &sig_ptr);

如果您这样做(而不是前面的),它会try 编码一个实际上不是签名的"签名",从而产生您观察到的伪2字节编码.

相反,您应该这样做:

    ECDSA_SIG *ec_sig = ECDSA_SIG_new();
    BN *rbn = BN_bin2bn(rval,sizeof(rval),NULL); 
    BN *sbn = BN_bin2bn(sval,sizeof(sval),NULL);
    // best style is to check and handle if r and/or s is NULL
    // but these calls can't actually fail in any sane system
    (void) ECDSA_SIG_set0(ec_sig,rbn,sbn); 
    // again you can check for return value != 1, but it won't happen

当然,还要纠正散列计算.

旁白:将unsigned char []变量(如sig_rsig_s)传递给const unsigned char *参数(如BN_bin2bn参数)时,不需要强制转换:任何类型为[qualified] array of T的左值,如果不是sizeof offsetof _Alignof的操作数,则会(自动转换)为pointer to [qualified] T,并且指向未限定T的任何指针都可以自动添加const和/或volatile的限定条件.因此,类型为unsigned char[32]sig_r自动变为unsigned char *,然后根据需要变为const unsigned char *.这是用C编程的基础知识(以及C++的"C子集",如果您不完全切换到std::vector或类似的语言),应该在任何课程的前两周或任何课本的两章内讨论.

C++相关问答推荐

获取二维数组的最大元素

Pure Win32 C(++)-除了替换控件的窗口程序之外,还有其他方法可以在输入时禁用按钮吗?

sizeof结果是否依赖于字符串的声明?

为什么我得到更多的256假阳性在PKZIP解密密钥验证?

为什么可以通过指向常量int的指针间接地改变整数的值?

堆栈帧和值指针

如何将字符串argv[]赋给C中的整型数组?

警告:C++中数组下标的类型为‘char’[-Wchar-subpts]

将fget()与strcMP()一起使用不是正确的比较

Ruby C Api处理异常

为什么即使在强制转换时,此代码也会溢出?

是否可以使用指针算法在不对齐的情况下在 struct 中相同类型的字段的连续序列之间移动?

我的程序在收到SIGUSR1信号以从PAUSE()继续程序时总是崩溃()

如何在CANbus RX/TX FIFO起始地址寄存器(ATSAME 51)的特定地址初始化数组?

我的C函数起作用了,但我不确定为什么

Boyer Moore算法的简单版本中的未定义行为

Zlib:解压缩大文件导致";无效代码长度设置";错误

我正在使用c学习数据 struct ,在学习堆栈时,我试图将中缀转换为后缀,并编写了这段代码.代码未给出输出

通过GTK';传递回调参数;s g_signal_connect()导致C中出现意外值

传递参数:C 和 C++ 中 array 与 *&array 和 &array[0] 的区别