X.590 sha1-DSA证书加密在Java平台与.net平台间的互通
X.509证书的读取在.net中对应的是System.Security.Cryptography.X509Certificates.X509Certificate2类。X509Certificate2支持RSA和DSA加密。对于RSA加密方式在MSDN上已经说的很清楚,但对于DSA加密方式的加密应用在MSDN上基本上没有提到。
本文阐述两平台间DSA加密的差异
一、证书与密钥的生成
在.net FrameWork下可以用自带的Makecert.exe工具生成密钥与证书。Makecert.exe只能生成RSA加密方式的证书,用Makecert.exe生成证书.pvk和.crt证书后,再用Cert2spc.exe工具生成.spc格式的文件,最后用pvk2pfx.ext工具生成我们想要的.pfx格式的文件。关于Makecert.exe、Cert2spc.exe、pvk2pfx.ext三个工具的使用可以到MSDN上查到。
.net中要生成DSA格式的密钥和公钥需要用到OpenSSL,找到OpenSSL的Win32版本后按以下步骤操作
① 生成DSA参数文件 openssl dsaparam -rand -genkey -out ca.pem 1024 ② 生成DSA私钥 openssl gendsa -des3 -out ca.key ca.pem ③ 生成CA的自签名证书 openssl req -new -x509 -key ca.key -out ca.crt -config openssl.cnf ④ 生成客户端CSR openssl req -new -key ca.key -out jfsc_Public.csr -days 1460 -config openssl.cnf ⑤ 用CA私钥进行签名,也可以到权威机构申请CA签名 openssl ca -in jfsc_Public.csr -out jfsc_Public.crt -cert ca.crt -days 1460 -keyfile ca.key -notext -config openssl.cnf ⑥ 生成PKCS12格式证书 openssl pkcs12 -export -inkey ca.key -in jfsc_Public.crt -out jfsc_Private.p12 ⑦ 将PKCS12格式证书导入本机证书存储单元,最后导出jfsc_Public.cer C=CH,O=iflytek,OU=jfsc,L=bj,S=bj,CN=uam,E=xuf22@hotmail.com生成私钥时会提示输入密码,这里用的是12345678,jfsc_Private.p12是私钥,jfsc_Public.cer是公钥在JDK平台中用KeyTool.exe生成DSA加密的私钥keystore和公钥.crt文件。
二、.net中如何用JDK的公钥.crt文件验证签名
JAVA的私钥keyStore文件在.net中是无法读取的,而公钥.crt文件是可以加载到X509Certificate2类中的。Java平台中用私钥签名得到16进制的字符串如下:
30 2C
02 14
1C FA 3A BB 5C 0F 37 FB D7 74 CB 51 E5 64 B5 76 B9 26 4F 7A
02 14
62 7E FF 2D 11 4B 2D D3 27 F3 8B 30 D1 6D B4 D2 78 5A 72 5B可以发现Java的签名是43字节的,而.net的签名只能是40位。 必须对java的签名做转换。
/* DER编码规则 DSA签名产生得到的是r和s两个整数。对于DSAWithSHA1来说,这两个整数都不会超过20字节。由于DSA签名需要使用一个随机数,所以每次签名的结果是不同的。 JAVA产生的DSAWithSHA1的签名结果是个下面ASN.1结构的DER编码: Dss-Sig-Value ::= SEQUENCE { r INTEGER, s INTEGER } .NET要求的签名结果40字节,前面20字节是r,后面20字节是s,采用大端字节序。 DER编码采用TLV的形式,每种类型有个Tag的编码,是为T部分的编码,接着是长度的编码,是为L部分的编码,最后是内容的编码,是为V部分的编码。 对于SEQUENCE来说,Tag的编码为0x30,其内容编码就是其容纳的ASN.1对象的逐个TLV编码拼起来。 对于INTEGER来说,Tag的编码为0x02,其内容编码就是使用最小的字节进行的补码形式。前9位不能全为1或者全为0。最高位为0表示正数,最高位为1表示负数。由于这里都是正数,所以如果最高位为1的话,前面要补0,也就是说最长的整数内容部分的编码长度为20+1=21字节。 长度的编码对于不超过127的来说,只占有一个字节,编码就是值本身。这里就是这种情况。 例子说明: Java的签名 30 2C 02 14 1C FA 3A BB 5C 0F 37 FB D7 74 CB 51 E5 64 B5 76 B9 26 4F 7A 02 14 62 7E FF 2D 11 4B 2D D3 27 F3 8B 30 D1 6D B4 D2 78 5A 72 5B 30表示是SEQUENCE 2C表示后面编码有0x2c字节 02表示是INTEGER 14表示后面编码有0x14字节 1C FA 3A BB 5C 0F 37 FB D7 74 CB 51 E5 64 B5 76 B9 26 4F 7A这部分就是r内容的编码 这里r内容的编码刚好是20自己,直接拷贝即可。如果小于20字节,则前面补0到20字节。如果是21字节,则去掉最前面的1个字节,这个字节应该是0,表示r是正数;如果这个字节不是0则签名是错的。 02 表示是INTEGER 14表示后面编码有0x14字节 62 7E FF 2D 11 4B 2D D3 27 F3 8B 30 D1 6D B4 D2 78 5A 72 5B这部分是s内容的编码。处理方式和r内容的编码一样。 最后转换得到.NET的签名为 1C FA 3A BB 5C 0F 37 FB D7 74 CB 51 E5 64 B5 76 B9 26 4F 7A 62 7E FF 2D 11 4B 2D D3 27 F3 8B 30 D1 6D B4 D2 78 5A 72 5B * */代码:public byte[] fromHex(byte[] sc) { byte[] res = new byte[sc.Length / 2]; for (int i = 0; i < sc.Length; i++) { byte c1 = (byte)(sc[i] - 48 < 17 ? sc[i] - 48 : sc[i] - 55); i++; byte c2 = (byte)(sc[i] - 48 < 17 ? sc[i] - 48 : sc[i] - 55); res[i / 2] = (byte)(c1 * 16 + c2); } return res; } public byte[] fromHexString(String hex) { return fromHex(System.Text.Encoding.Default.GetBytes(hex)); } private bool VerifySignature(string originalString) { try { byte[] hashByteToVerify; byte[] signByte; string signatureConvert = DERDecode(signature); signByte = fromHexString(signatureConvert); X509Certificate2 x509_Cer2 = new X509Certificate2("path/jfsc_Public.cer"); dsa = (DSACryptoServiceProvider)x509_Cer2.PublicKey.Key; hashByteToVerify = System.Text.Encoding.Default.GetBytes(originalString); bool b = dsa.VerifyData(hashByteToVerify, signByte); return b; } } public string DERDecode(string DERStr) { string Result = ""; string R = ""; string S = ""; byte[] RByte = new byte[20]; byte[] SByte = new byte[20]; byte[] Content = fromHexString(DERStr); string IntRStr = Content[3].ToString(); int IntRLength = int.Parse(IntRStr); int j = 0; if (IntRLength == 20) { for (int i = 0; i < 20; i++) { RByte[i] = Content[4 + i]; } j = 24; } else if (IntRLength > 20) { for (int i = 0; i < 20; i++) { RByte[i] = Content[5 + i]; } j = 25; } else { int NotEnough = 20 - IntRLength; for (int i = 0; i < NotEnough; i++) { byte[] tmp = new byte[] { 00 }; RByte[i] = tmp[0]; } for (int i = 0; i < NotEnough; i++) { RByte[NotEnough + i] = Content[4 + i]; } j = 24 - IntRLength; } int IntSLength = int.Parse(Content[j + 1].ToString()); if (IntSLength == 20) { for (int i = 0; i < 20; i++) { SByte[i] = Content[j + 2 + i]; } } else if (IntSLength > 20) { for (int i = 0; i < 20; i++) { SByte[i] = Content[j + 3 + i]; } } else { int NotEnough = 20 - IntSLength; for (int i = 0; i < NotEnough; i++) { byte[] tmp = new byte[] { 00 }; SByte[i] = tmp[0]; } for (int i = 0; i < IntSLength; i++) { SByte[NotEnough + i] = Content[j + 2 + i]; } } R = toHexString(RByte); S = toHexString(SByte); Result = R + S; return Result; }
三、将.net的签名转后java平台才可能对签名验证
public string DEREncode(byte[] b) { string str = toHexString(b); string newstr1 = str.Substring(0, 40); string newstr2 = str.Substring(40, 40); string newstr = "302C0214" + newstr1 + "0214" + newstr2; return newstr; } private string SigeXml(string originalString) { X509Certificate2 x509_Cer2 = new X509Certificate2("path/jfsc_Private.p12"); dsa = (DSACryptoServiceProvider)x509_Cer2.PublicKey.Key; byte[] xmlByte = Encoding.Default.GetBytes(originalString); byte[] hashByte = rsa.SignData(xmlByte); return DEREncode(hashByte); }
感谢素未谋面的仝工给予的帮助
曾经年少多少事 而今皆付谈笑中!