1. 前言
在与合作方进行通信时,为了保证数据完整性,通常会使用数字签名。
有时由于合作方的理解不一致,或使用方法不正确,可能导致合作方生成签名或验证签名时失败。
在遇到以上情况时,通常比较难快速定位问题,可以使用openssl对签名进行检查,确认导致验证签名失败的具体原因。
以下需要使用keytool与openssl命令,可查看https://docs.oracle.com/javase/8/docs/technotes/tools/unix/keytool.html、https://www.openssl.org/docs/manmaster/man1/。
以下假设应用程序使用Java开发,将密钥对内容保存keystore文件中,生成keystore文件时密钥算法为RSA,签名算法为SHA256withRSA。
keystore文件生成命令略。
2. 使用openssl验证Java签名结果
使用openssl命令可以验证Java生成的签名结果是否正确。
首先需要将keystore文件中的公钥导出,再使用openssl命令根据公钥对签名进行验证。
2.1 导出公钥
在将keystore文件中的公钥导出时,需要先从keystore文件导出X.509证书文件,再从X.509证书文件导出公钥。
l 导出X.509证书文件
使用Java的keytool命令从keystore文件导出X.509证书文件,命令如下。
当指定-rfc选项时,导出的证书文件为Base64格式;若不指定-rfc选项,导出的证书文件为二进制格式。
keystore=[需要导出的keystore文件名/完整路径] alias=[keystore文件需要导出证书文件的别名] storepass=[keystore文件的密码] exportcert=[导出后的证书文件名/完整路径]
keytool -exportcert -alias $alias -keystore $keystore -file $exportcert -storepass $storepass -rfc |
示例: keystore=test.jks alias=test_alias storepass=test_pwd exportcert=test.cer
keytool -exportcert -alias $alias -keystore $keystore -file $exportcert -storepass $storepass -rfc |
导出成功时,提示为“Certificate stored in file <...>”。
导出后的证书文件内容如下所示:
-----BEGIN CERTIFICATE----- MIIDCTCCAfGgAwIBAgIEDt12JDANBgkqhkiG9w0BAQsFADA1MQ8wDQYDVQQKDAZvX3Rlc3QxEDAO ... E1phqmUG9Yz7jOuap4AxkTjsj1VH2Y7u9T5jhVCqrumKxLy9QwYbZQ== -----END CERTIFICATE----- |
l 导出公钥
使用openssl的x509命令从X.509证书文件导出公钥,命令如下。
使用-noout选项可以不显示证书文件内容,使用-pubkey选项可以以PEM格式输出证书的公钥信息。
exportcert=[前一步导出的证书文件名/完整路径] publickey=[需要导出的公钥文件名/完整路径]
openssl x509 -in $exportcert -noout -pubkey > $publickey |
示例: exportcert=test.cer publickey=public.pem openssl x509 -in $exportcert -noout -pubkey > $publickey |
导出后的公钥文件内容如下所示:
-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwS18K4RE0BHIZ8a1h08w ... zwIDAQAB -----END PUBLIC KEY----- |
2.2 验证签名
使用openssl的dgst命令的-verify选项对Java生成的签名结果进行验证,命令如下。
签名结果的生成算法:需要指定生成签名结果时使用的算法,dgst命令支持的算法包括“-md5|-md4|-md2|-sha1|-sha|-mdc2|-ripemd160|-dss1”等。当Java程序生成签名结果使用“MD5withRSA/Sha1withRSA/Sha256withRSA”算法时,该参数应指定为“md5/sha1/sha256”。
二进制的签名结果文件:需要指定Java生成的签名结果文件,该文件内容应为二进制,不能经过其他编码处理。
生成签名的原始数据文件:需要指定为Java生成签名结果时使用的原始数据保存的文件,文件内容应仅包含签名原始数据,不能包含多余的内容,如回车换行等。
algorithm=[签名结果的生成算法] publickey=[前一步导出的公钥文件名/完整路径] sign_data=[二进制的签名结果文件名/完整路径] src_file=[生成签名的原始数据文件名/完整路径] openssl dgst -$algorithm -verify $publickey -signature $sign_data < $src_file |
示例: algorithm=sha256 publickey=public.pem sign_data=Sha256withRSA.sign src_file=src.txt openssl dgst -$algorithm -verify $publickey -signature $sign_data < $src_file |
当验证签名通过时,提示“Verified OK”;当验证签名不通过时,提示“Verification Failure”;当指定的参数错误时,提示为其他。
3. 使用openssl生成签名
使用openssl命令可以生成签名。
首先需要将keystore文件中的私钥导出,再使用openssl命令根据私钥对数据进行签名。
3.1 导出私钥
在将keystore文件中的私钥导出时,需要先从keystore文件导出P12文件(包含私钥与证书),再从P12文件导出私钥。
l 导出P12文件
使用Java的keytool命令从keystore文件导出P12文件,命令如下。
keystore=[需要导出的keystore文件名/完整路径] alias=[keystore文件需要导出证书文件的别名] storepass=[keystore文件的密码] keypass=[keystore文件中alias对应的密钥的密码] p12_file=[生成的p12文件名/完整路径] p12_pwd=[生成的p12文件的密码]
keytool -importkeystore -srckeystore $keystore -destkeystore $p12_file -srcstoretype JKS -deststoretype PKCS12 -srcstorepass $storepass -deststorepass $p12_pwd -srcalias $alias -destalias $alias -srckeypass $keypass -destkeypass $p12_pwd |
示例:
keystore=test.jks alias=test_alias storepass=test_pwd keypass=test_pwd p12_file=tmp.p12 p12_pwd=test_p12_pwd
keytool -importkeystore -srckeystore $keystore -destkeystore $p12_file -srcstoretype JKS -deststoretype PKCS12 -srcstorepass $storepass -deststorepass $p12_pwd -srcalias $alias -destalias $alias -srckeypass $keypass -destkeypass $p12_pwd |
导出成功时,无提示信息。
导出的P12文件为二进制形式。
l 导出私钥
使用openssl的pkcs12与rsa命令从P12文件导出私钥(生成keystore时选择的密钥算法为RSA,此时需要使用openssl的rsa命令),命令如下。
p12_file=[生成的p12文件名/完整路径] p12_pwd=[生成的p12文件的密码] tmp_pem=[导出的包含私钥与证书的临时文件名/完整路径] final_pem=[导出的最终私钥文件名/完整路径]
openssl pkcs12 -in $p12_file -nodes -out $tmp_pem -passin pass:$p12_pwd openssl rsa -in $tmp_pem > $final_pem |
示例: p12_file=tmp.p12 p12_pwd=test_p12_pwd tmp_pem=tmp.pem final_pem=final.pem
openssl pkcs12 -in $p12_file -nodes -out $tmp_pem -passin pass:$p12_pwd openssl rsa -in $tmp_pem > $final_pem |
首先使用openssl的pkcs12命令,将二进制形式的P12文件中的证书与私钥信息输出,指定-nodes选项,使输出私钥时不加密。pkcs12命令执行成功时,提示“MAC verified OK”。生成的包含证书与私钥信息的文件内容如下所示:
Bag Attributes friendlyName: test_alias localKeyID: 54 69 6D 65 20 31 35 35 33 36 39 30 32 30 37 33 39 34 Key Attributes: <No Attributes> -----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCSXEfNNyk+5FtY ... bqXK9vOoY4w+pCshUrDD7SFk -----END PRIVATE KEY----- Bag Attributes friendlyName: test_alias localKeyID: 54 69 6D 65 20 31 35 35 33 36 39 30 32 30 37 33 39 34 subject=/O=o_test/OU=ou_test/CN=cn.test issuer=/O=o_test/OU=ou_test/CN=cn.test -----BEGIN CERTIFICATE----- MIIDCTCCAfGgAwIBAgIER2IkjTANBgkqhkiG9w0BAQsFADA1MQ8wDQYDVQQKDAZv ... Sso9pt+lcXEhKw3zWw== -----END CERTIFICATE----- |
再使用openssl的rsa命令,将包含证书与私钥信息的文件内容导出为RSA私钥。rsa命令执行成功时,提示“writing RSA key”。
导出后的私钥文件内容如下所示:
-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAwS18K4RE0BHIZ8a1h08wu8rypMbYirOQxcsdWEIl6eRz4m6o ... t0p8ij9YNGzM5a8uDRZpIdOm3Z6X20d+PKj5Ucl+ns2SObSuSatSfw== -----END RSA PRIVATE KEY----- |
3.2 生成签名
使用openssl的dgst命令的-sign选项生成签名内容,命令如下。
签名结果的生成算法:略,见前文。
生成的签名内容的文件:指定签名内容需要保存到哪个文件。
生成签名的原始数据文件:指定生成签名内容时使用的原始数据的文件,文件内容应仅包含签名原始数据,不能包含多余的内容,如回车换行等。
algorithm=[签名结果的生成算法] privatekey=[前一步导出的私钥文件名/完整路径] sign_file=[生成的签名内容的文件名/完整路径] src_file=[生成签名的原始数据文件名/完整路径]
openssl dgst -$algorithm -sign $privatekey <$src_file > $sign_file |
示例: algorithm=sha256 privatekey=final.pem sign_file=sha256.sign src_file=src.txt
openssl dgst -$algorithm -sign $privatekey <$src_file > $sign_file |
生成的签名内容为二进制形式,可以与Java生成的签名内容比较是否一致。