如何手动计算和验证证书签名请求的签名

How to manually calculate and verify the signature of a certificate signing request

openssl genrsa -out test1 2048

openssl req -new -key test1 -subj "/CN=foo" -out foo.csr

以下是我的疑惑:

  1. 它在计算签名时考虑了哪些数据字段?
  2. 我们可以手动创建签名并使用 CSR 中提到的签名进行验证吗?

用于计算签名的数据几乎是 CSR 中的所有内容,除了签名算法和签名。这在 RFC 2986.

中称为 certificationRequestInfo

第二个问题:我们如何验证签名?

简短回答:请openssl为您完成。

到目前为止最简单的方法是要求 openssl 自己验证它:

$ openssl genrsa -out test.key 2048
$ openssl req -new -key test.key -subj "/CN=foo" -out foo.csr
$ openssl req -in foo.csr -verify -noout
verify OK

完成!


冗长(乏味)的答案:手动完成

好的,所以你真的想手动完成。让我们试一试。

综上所述,我们首先需要从CSR中提取所需的信息。转储 ASN.1 结构,我们得到:

$ openssl asn1parse -i -in foo.csr 
    0:d=0  hl=4 l= 595 cons: SEQUENCE          
    4:d=1  hl=4 l= 315 cons:  SEQUENCE          
    8:d=2  hl=2 l=   1 prim:   INTEGER           :00
   11:d=2  hl=2 l=  14 cons:   SEQUENCE          
   13:d=3  hl=2 l=  12 cons:    SET               
   15:d=4  hl=2 l=  10 cons:     SEQUENCE          
   17:d=5  hl=2 l=   3 prim:      OBJECT            :commonName
   22:d=5  hl=2 l=   3 prim:      UTF8STRING        :foo
   27:d=2  hl=4 l= 290 cons:   SEQUENCE          
   31:d=3  hl=2 l=  13 cons:    SEQUENCE          
   33:d=4  hl=2 l=   9 prim:     OBJECT            :rsaEncryption
   44:d=4  hl=2 l=   0 prim:     NULL              
   46:d=3  hl=4 l= 271 prim:    BIT STRING        
  321:d=2  hl=2 l=   0 cons:   cont [ 0 ]        
  323:d=1  hl=2 l=  13 cons:  SEQUENCE          
  325:d=2  hl=2 l=   9 prim:   OBJECT            :sha256WithRSAEncryption
  336:d=2  hl=2 l=   0 prim:   NULL              
  338:d=1  hl=4 l= 257 prim:  BIT STRING        

这是一个结构化的 CertificationRequest,由 RFC 2986 定义为:

CertificationRequest ::= SEQUENCE {
    certificationRequestInfo CertificationRequestInfo,
    signatureAlgorithm       AlgorithmIdentifier{{ SignatureAlgorithms }},
    signature                BIT STRING
}

DER-encoded ASN.1 格式的 certificationRequestInfo(结构细节见 RFC)使用 signatureAlgorithm 中描述的算法和生成 [=22= 的私钥进行签名].


让我们从 CSR 中提取我们需要的所有部分。 strparse 值是您要导出的偏移量,这是上面输出中每行的第一个数字。

# Extract the certificationRequestInfo (data to be signed)
$ openssl asn1parse -in foo.csr -strparse 4 -out info.der

# Extract the public key.
$ openssl req -pubkey -in foo.csr -noout -out pub.pem

# Alternatively, you can use:
$ openssl asn1parse -in foo.csr -strparse 27 -out tmp.der
$ openssl rsa -pubin -inform DER -in tmp.der -out pub.pem

# Extract the raw signature bytes:
$ openssl asn1parse -in foo.csr -strparse 338 -out sig.raw
    0:d=0  hl=2 l=  70 cons: cont [ 3 ]        
Error in encoding
139935063934272:error:0D07209B:asn1 encoding routines:ASN1_get_object:too long:../crypto/asn1/asn1_lib.c:91:

忽略最后一个错误,这是因为提取的数据是原始签名字节,不是 ASN.1 编码的。 openssl还是很开心的写到文件里

我们现在有以下文件:

  • info.der: 签名的DER-encoded数据
  • pub.pem:CSR 请求者的 public 密钥
  • sig.raw:CSR 中包含的签名

让我们使用 public 密钥验证 RSA 签名(因为这是签名算法所说的)并提取原始哈希值:

$ openssl rsautl -verify -pubin -inkey pub.pem -in sig.raw -out hash.der
$ openssl asn1parse -i -in hash.der -inform DER
    0:d=0  hl=2 l=  49 cons: SEQUENCE          
    2:d=1  hl=2 l=  13 cons:  SEQUENCE          
    4:d=2  hl=2 l=   9 prim:   OBJECT            :sha256
   15:d=2  hl=2 l=   0 prim:   NULL              
   17:d=1  hl=2 l=  32 prim:  OCTET STRING      [HEX DUMP]:192E0909DABC7454006628AA3F7FB009AFA62A17A44908CAE5E166E528DCDD11

它没有失败,所以我们已经知道 public 密钥与用于签署数据的私钥相匹配。

最后一部分,长 OCTET STRING 是由 CSR 请求者计算的消息的原始哈希值:

192e0909dabc7454006628aa3f7fb009afa62a17a44908cae5e166e528dcdd11

让我们计算 certificationRequestInfo:

sha256 散列(再一次:因为签名算法告诉我们这样做)
$ sha256sum info.der 
192e0909dabc7454006628aa3f7fb009afa62a17a44908cae5e166e528dcdd11  info.der

耶!哈希值等于从签名中提取的哈希值。


哈希值匹配,签名由与 CSR 中列出的 public 密钥相对应的私钥签名。 这是一个有效的 CSR

完成!看,我说这会很乏味。