人类常用权威的话语来证明自己的观点,这种方式可以说是一种签名。当然,这种方式存在一个严重的缺陷:无法证明那个生成的名人确实说过这句话。


同样的,在互联网上,我们需要用一种方式来验证确认身份信息。其中的一个方法就是数字签名。

前面介绍过 RSA 算法,因为它能够实现非对称的加密所以还可以用来进行数字签名。比如:我在网站上公布了自己的公钥,然后每次发送消息的时候会附上使用私钥对这个消息签名结果。这样,别人就无法冒充我发布消息。这次展示使用 OpenSSL 实现UEFI下面的签名和验证。

第一步,生成RSA密钥对(我不清楚为什么文章都喜欢将这个直接称作“私钥”,实际上这样生成的结果是包括公钥部分的,索性这里我称之为密钥包)。

openssl genrsa -out rsa_private_key.pem 1024

生成的密钥包内容如下:

-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDTiExu8F6X/VWPqPmzBc3mqUfMvOB0xtaTZAQbSfyt+uqxu7GN
aStGpdJvCuOFL7+FFXSFLJfmZsiYRaiOSpPDDuQLBkG0m/ABk8pLtobEy6UgJPpS
6Oiku1618+uwYKcZpPj2Ftu5d+FFMiS23SCI8zhLcF3fM2J7/IXudCOErQIDAQAB
AoGABbgtuOIu7JUg9x1ugvSpOI9jLZn9x6qIqruNkN9TQbEDH4Mfrd8mGGbrZa05
saQ03XhTCjbGdKhazCM2B4Lks9jb0ovA2CUvuX+sxUHXdgEFekEv/VVIzem3qt0F
kMV+lSX76F/CkV4XXOO4H1rYja7BObl/jFMjTpRgPh0g4AECQQD3MCGCwqqQvGBV
Lw8cJHj7E9bMK8NWauONJfYL0D+9/ylas+xS73iqLcSpWcchWDjbM3T2tYAeZLrf
i0VmHm4BAkEA2xLFKytC9dbIDJS+0316jZvK/AWsSDbKo+Fn+DnO9x6ZhstX062t
7WEDRHT3ZR0vQTS7f9SayxN3MMwSB88urQJBAOI2ofRQwleCjYZncqSGnFDqbwCa
bEGBwI1D2FAnXK47/VSMpBGiJgNXr0psZtgVLLMt/DRrFby64moBwpkZ8AECQQC3
7zWOfj81S8UhEw55YYQxO1odaeHxq9dN62Yw+tBzmcSLcVVnTA6ZHPfyVUaWJf/T
/qNiu63PzaMoXF7TIbftAkBcpDvw4T3kynR1mXyuC1jmvenYZGtfdhtCgqBRt4Z2
MFeMsqkFNDzuJbHdq1vdDUl2Oh4XyVyOGNc5hxiFrx8a
-----END RSA PRIVATE KEY-----

第二步,从这个密钥包分离出来公钥。

Openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem

公钥文件名是rsa_public_key.pem,内容如下:

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDTiExu8F6X/VWPqPmzBc3mqUfM
vOB0xtaTZAQbSfyt+uqxu7GNaStGpdJvCuOFL7+FFXSFLJfmZsiYRaiOSpPDDuQL
BkG0m/ABk8pLtobEy6UgJPpS6Oiku1618+uwYKcZpPj2Ftu5d+FFMiS23SCI8zhL
cF3fM2J7/IXudCOErQIDAQAB
-----END PUBLIC KEY-----

这个文件是可以公开的。前面提到的密钥包是需要保密的。

到这步,已经准备好了公钥和私钥,可以用

openssl rsa -in rsa_private_key.pem -text

解析出我们要定义在代码中的内容

Private-Key: (1024 bit)
modulus:   //这是公钥RsaN[]  需要特别注意,下面是 127 Byte,开头多了一个 00
    00:d3:88:4c:6e:f0:5e:97:fd:55:8f:a8:f9:b3:05:
    cd:e6:a9:47:cc:bc:e0:74:c6:d6:93:64:04:1b:49:
    fc:ad:fa:ea:b1:bb:b1:8d:69:2b:46:a5:d2:6f:0a:
    e3:85:2f:bf:85:15:74:85:2c:97:e6:66:c8:98:45:
    a8:8e:4a:93:c3:0e:e4:0b:06:41:b4:9b:f0:01:93:
    ca:4b:b6:86:c4:cb:a5:20:24:fa:52:e8:e8:a4:bb:
    5e:b5:f3:eb:b0:60:a7:19:a4:f8:f6:16:db:b9:77:
    e1:45:32:24:b6:dd:20:88:f3:38:4b:70:5d:df:33:
    62:7b:fc:85:ee:74:23:84:ad
publicExponent: 65537 (0x10001)  
privateExponent:  //这是私钥部分,RsaD[]
    05:b8:2d:b8:e2:2e:ec:95:20:f7:1d:6e:82:f4:a9:
    38:8f:63:2d:99:fd:c7:aa:88:aa:bb:8d:90:df:53:
    41:b1:03:1f:83:1f:ad:df:26:18:66:eb:65:ad:39:
    b1:a4:34:dd:78:53:0a:36:c6:74:a8:5a:cc:23:36:
    07:82:e4:b3:d8:db:d2:8b:c0:d8:25:2f:b9:7f:ac:
    c5:41:d7:76:01:05:7a:41:2f:fd:55:48:cd:e9:b7:
    aa:dd:05:90:c5:7e:95:25:fb:e8:5f:c2:91:5e:17:
    5c:e3:b8:1f:5a:d8:8d:ae:c1:39:b9:7f:8c:53:23:
4e:94:60:3e:1d:20:e0:01

第三步,有了上面的密钥包(其实是其中的私钥)即可对消息进行签名。例如,我们使用“This message from lab-z.com”作为被签名的字符串,将这一段存放在 string.txt 文件中。然后对其签名:

openssl dgst -sha1 -sign rsa_private_key.pem –out string.sign string.txt

签名结果如下:

第四步,签名的校验。使用下面的命令,这次用到的是公钥,然后针对 string.txt 校验签名结果是否和 string.sign内容相同。

openssl dgst -verify rsa_public_key.pem -sha1 -signature string.sign string.txt

如果结果相同,会输出Verified OK ,否则输出Verification Failure。

上面就是使用 OpenSSL.exe 来完成 RSA 签名校验的过程。接下来介绍使用 UEFI 完成这些操作。代码改编自 UDK2017 中的 CryptoPkg 下面 Application 的例子(但是从 UDK2018开始这部分代码被移除了)

/** @file
  Application for Cryptographic Primitives Validation.

Copyright (c) 2009 - 2016, Intel Corporation. All rights reserved.<BR>
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution.  The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php

THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/
#include <Uefi.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiLib.h>
#include <Library/UefiApplicationEntryPoint.h>
#include <Library/DebugLib.h>
#include <Library/BaseCryptLib.h>

#define  RSA_MODULUS_LENGTH  1024

//
// Public Modulus of RSA Key
//
GLOBAL_REMOVE_IF_UNREFERENCED CONST UINT8 RsaN[] = {
    0xd3,0x88,0x4c,0x6e,0xf0,0x5e,0x97,0xfd,0x55,0x8f,0xa8,0xf9,0xb3,0x05,
    0xcd,0xe6,0xa9,0x47,0xcc,0xbc,0xe0,0x74,0xc6,0xd6,0x93,0x64,0x04,0x1b,0x49,
    0xfc,0xad,0xfa,0xea,0xb1,0xbb,0xb1,0x8d,0x69,0x2b,0x46,0xa5,0xd2,0x6f,0x0a,
    0xe3,0x85,0x2f,0xbf,0x85,0x15,0x74,0x85,0x2c,0x97,0xe6,0x66,0xc8,0x98,0x45,
    0xa8,0x8e,0x4a,0x93,0xc3,0x0e,0xe4,0x0b,0x06,0x41,0xb4,0x9b,0xf0,0x01,0x93,
    0xca,0x4b,0xb6,0x86,0xc4,0xcb,0xa5,0x20,0x24,0xfa,0x52,0xe8,0xe8,0xa4,0xbb,
    0x5e,0xb5,0xf3,0xeb,0xb0,0x60,0xa7,0x19,0xa4,0xf8,0xf6,0x16,0xdb,0xb9,0x77,
    0xe1,0x45,0x32,0x24,0xb6,0xdd,0x20,0x88,0xf3,0x38,0x4b,0x70,0x5d,0xdf,0x33,
    0x62,0x7b,0xfc,0x85,0xee,0x74,0x23,0x84,0xad
  };

//
// Public Exponent of RSA Key

GLOBAL_REMOVE_IF_UNREFERENCED CONST UINT8 RsaE[] = { 0x01, 0x00,0x01 };

//
// Private Exponent of RSA Key
//
GLOBAL_REMOVE_IF_UNREFERENCED CONST UINT8 RsaD[] = {
    0x05,0xb8,0x2d,0xb8,0xe2,0x2e,0xec,0x95,0x20,0xf7,0x1d,0x6e,0x82,0xf4,0xa9,
    0x38,0x8f,0x63,0x2d,0x99,0xfd,0xc7,0xaa,0x88,0xaa,0xbb,0x8d,0x90,0xdf,0x53,
    0x41,0xb1,0x03,0x1f,0x83,0x1f,0xad,0xdf,0x26,0x18,0x66,0xeb,0x65,0xad,0x39,
    0xb1,0xa4,0x34,0xdd,0x78,0x53,0x0a,0x36,0xc6,0x74,0xa8,0x5a,0xcc,0x23,0x36,
    0x07,0x82,0xe4,0xb3,0xd8,0xdb,0xd2,0x8b,0xc0,0xd8,0x25,0x2f,0xb9,0x7f,0xac,
    0xc5,0x41,0xd7,0x76,0x01,0x05,0x7a,0x41,0x2f,0xfd,0x55,0x48,0xcd,0xe9,0xb7,
    0xaa,0xdd,0x05,0x90,0xc5,0x7e,0x95,0x25,0xfb,0xe8,0x5f,0xc2,0x91,0x5e,0x17,
    0x5c,0xe3,0xb8,0x1f,0x5a,0xd8,0x8d,0xae,0xc1,0x39,0xb9,0x7f,0x8c,0x53,0x23,
    0x4e,0x94,0x60,0x3e,0x1d,0x20,0xe0,0x01
  };

//
// Known Answer Test (KAT) Data for RSA PKCS#1 Signing
//
GLOBAL_REMOVE_IF_UNREFERENCED CONST CHAR8 RsaSignData[] = "This message from lab-z.com";

//
// Known Signature for the above message, under SHA-1 Digest
//
GLOBAL_REMOVE_IF_UNREFERENCED CONST UINT8 RsaPkcs1Signature[] = {
0x61,0xA2,0xDD,0x93,0x1A,0xA6,0x1B,0x46,0x2A,0x84,0xC8,0x7A,0x74,0xBB,0x23,0x44,
0x43,0xD6,0xE9,0x2A,0x30,0xAF,0x2D,0x13,0x0A,0x74,0x26,0x78,0xF6,0x42,0x23,0x4D,
0x55,0x85,0xC9,0x42,0xA8,0x4A,0xAC,0x2C,0x46,0x76,0xF5,0x34,0xC7,0x57,0x3B,0x2F,
0x4B,0xF9,0x42,0x03,0x1F,0x80,0xCE,0xF2,0xD7,0xA4,0x8C,0xBB,0xBF,0x37,0x60,0x4A,
0x32,0x3A,0xF2,0x82,0x95,0xF3,0x11,0x40,0x2E,0x45,0x4B,0x2E,0x02,0xBA,0xAA,0xFC,
0x29,0x8D,0xEC,0x56,0xC6,0xCD,0x97,0x06,0xDE,0x52,0x85,0xDB,0x1B,0x17,0xF1,0x39,
0xBB,0x6B,0x8C,0xAA,0xFE,0xEC,0xD4,0xA7,0x96,0x6F,0x22,0xCD,0x4B,0x6D,0x01,0x0B,
0x00,0xA1,0xDF,0x7F,0xA4,0xA0,0xD2,0xC4,0x09,0x0C,0xB0,0x4A,0x7A,0xA2,0xE2,0x93
};

/**
  Entry Point of Cryptographic Validation Utility.

  @param  ImageHandle  The image handle of the UEFI Application.
  @param  SystemTable  A pointer to the EFI System Table.

  @retval EFI_SUCCESS       The entry point is executed successfully.
  @retval other             Some error occurs when executing this entry point.

**/
EFI_STATUS
EFIAPI
CryptestMain (
  IN     EFI_HANDLE                 ImageHandle,
  IN     EFI_SYSTEM_TABLE           *SystemTable
  )
{
  VOID     *Rsa;
  UINT8    HashValue[SHA1_DIGEST_SIZE];
  UINTN    HashSize;
  UINTN    CtxSize;
  VOID     *Sha1Ctx;
  UINT8    *Signature;
  UINTN    SigSize;
  BOOLEAN  Status;
  UINTN i;
  UINT8    sign[SHA1_DIGEST_SIZE];

  RandomSeed (NULL, 0);

  //
  // SHA-1 Digest Message for PKCS#1 Signature 
  //
  Print (L"Hash Original Message ...\n");
  HashSize = SHA1_DIGEST_SIZE;
  ZeroMem (HashValue, HashSize);
  CtxSize = Sha1GetContextSize ();
  Sha1Ctx = AllocatePool (CtxSize);

  Status  = Sha1Init (Sha1Ctx);
  if (!Status) {
    Print (L"[Fail]");
    return EFI_ABORTED;
  }

  Status  = Sha1Update (Sha1Ctx, RsaSignData, AsciiStrLen (RsaSignData));
  if (!Status) {
    Print (L"[Fail]");
    return EFI_ABORTED;
  }

  Status  = Sha1Final (Sha1Ctx, HashValue);
  if (!Status) {
    Print (L"[Fail]");
    return EFI_ABORTED;
  }
  
  for (i=0;i<HashSize;i++) {Print (L"%02X  ",HashValue[i]);}
  
  FreePool (Sha1Ctx);

  //
  // Sign RSA PKCS#1-encoded Signature
  //
  Print (L"PKCS#1 Signature ...\n");

  Rsa = RsaNew ();
  if (Rsa == NULL) {
    Print (L"[Fail]t\n");
    return EFI_ABORTED;
  }

  Status = RsaSetKey (Rsa, RsaKeyN, RsaN, sizeof (RsaN));
  if (!Status) {
    Print (L"[Fail]p\n");
    return EFI_ABORTED;
  }

  Status = RsaSetKey (Rsa, RsaKeyE, RsaE, sizeof (RsaE));
  if (!Status) {
    Print (L"[Fail]h\n");
    return EFI_ABORTED;
  }
  //Private Key
  Status = RsaSetKey (Rsa, RsaKeyD, RsaD, sizeof (RsaD));
  if (!Status) {
    Print (L"[Fail]q\n");
    return EFI_ABORTED;
  }

  SigSize = 0;
  Status  = RsaPkcs1Sign (Rsa, HashValue, HashSize, sign, &amp;SigSize);
  if (Status || SigSize == 0) {
    return EFI_ABORTED;
  }
  Signature = AllocatePool (SigSize);
  Status  = RsaPkcs1Sign (Rsa, HashValue, HashSize, Signature, &amp;SigSize);
  if (!Status) {
    Print (L"[Fail]y\n");
    return EFI_ABORTED;
  }

  for (i=0;i<SigSize;i++) { Print(L"%02X  ",Signature[i]);} Print(L" \n");
  
  if (SigSize != sizeof (RsaPkcs1Signature)) {
    Print (L"[Fail]\n");
    return EFI_ABORTED;
  }

  if (CompareMem (Signature, RsaPkcs1Signature, SigSize) != 0) {
    Print (L"[Fail]a2\n");
    return EFI_ABORTED;
  }

  //
  // Release Resources
  //
  RsaFree (Rsa);
  Print (L"Release RSA Context ... [Pass]");

  Print (L"\n");

  return EFI_SUCCESS;
}

运行结果如下:

可以看到代码是先进行了一次 SHA1 运算,然后再用私钥进行签名的。我猜测这样操作的原因是为了避免 RSA运算缓慢和安全性方面的考虑。

完整的代码下载

参考:

  1. https://www.cnblogs.com/gordon0918/p/5382541.html openssl 摘要和签名验证指令dgst使用详解
  2. https://www.cnblogs.com/liliyang/p/9738964.html (4) openssl rsa/pkey(查看私钥、从私钥中提取公钥、查看公钥)

Leave a Reply

电子邮件地址不会被公开。 必填项已用*标注

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>