一.RSA算法简介 关于RSA加密算法可以参考:http://zh.wikipedia.org/wiki/RSA%E5%8A%A0%E5%AF%86%E6%BC%94%E7%AE%97%E6%B3%95 大体是先生成两个大素数p和q,再生成e,e和(p-1)*(q-1)互素。 取p和q的乘积:n=p*q 为公共模数。 再生成正整数d,满足d*e-1可以被(p-1)*(q-1)整除。 这样d就为私钥,(e,n)为公钥,形成rsa的公私钥对。 其中n的二进制位称为该密钥长度,密钥越长越难破解,也就越安全。 RSA算法 : 首先, 找出三个数, p, q, r, 其中 p, q 是两个相异的质数, r 是与 (p-1)(q-1) 互质的数...... p, q, r 这三个数便是 private key 接著, 找出 m, 使得 rm == 1 mod (p-1)(q-1)..... 这个 m 一定存在, 因为 r 与 (p-1)(q-1) 互质, 用辗转相除法就可以得到了..... 再来, 计算 n = pq....... m, n 这两个数便是 public key 编码过程是, 若资料为 a, 将其看成是一个大整数, 假设 a < n.... 如果 a >= n 的话, 就将 a 表成 s 进位 (s <= n, 通常取 s = 2^t), 则每一位数均小於 n, 然後分段编码...... 接下来, 计算 b == a^m mod n, (0 <= b < n), b 就是编码後的资料...... 解码的过程是, 计算 c == b^r mod pq (0 <= c < pq), 於是乎, 解码完毕...... 这段话中:m就是Exponent,n是Modulus,p是P,q是Q,r是D,而InverseQ * p == 1 mod q ============================================================================ RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(1024); //1024 bit key RSAParameters par = rsa.ExportParameters(false); // export the public key string str = rsa.ToXmlString(true); str 值如下: ======================== <RSAKeyValue> <Modulus> tuNKKEhLHftRx81wHKQgunAHSUmLwBWBMNqBEZvZSAmntlzJPN3Yy7N53lLpeacl9M1J0LjeFyyOzpWwiYsapyDo4vlGzYcUy0AojMNW5DXuG7/UK0+LWdW2gs6mhN3CWwmynqCKnvrwv993m/iPiFXUyqQu+OhSsca7p4XZnn8= </Modulus> <Exponent>AQAB</Exponent> <P>7X5WxuLAIYcoXt5LKelRzUZT8hjkU4qIikHBplK8TUNU5y1rDlQ9Awdb9mTIf2sILUDT7ryZZ6KlIFDfCVu3IQ==</P> <Q>xSOlCaMGBOo15EG1zwQJl1zhKicAXtzc/1nlnWiRz0cr5PiBdYJE2RnmiDNgOvz/WiU1RTvoWFTgQy5erXPBnw==</Q> <DP>jeIJk75JzH4Dt1GUlBBpJ3rZkewfFG3SDs8kEuqgxoPwSEBREflvjcyquzQQuFbIRsjRFHKmL0zy27CU3vFlIQ==</DP> <DQ>OQYXfhMe8ZsB3bW4Llp/n73pD7VaYRZIAsDxLzgJPUjcBI5xfBkjax0X1vvtcQKvplau8wjiK3LZr/Ugw9GBBw==</DQ> <InverseQ>MjXjlXM7NuJWahnVSSD2JLeDDjwEyat7xyG+9mgWCMUSM/2vja4v9U+exOYu8T4fnwKq5+hKlA3E2Aw9IjeIxg==</InverseQ> <D>h7zgME+fuOvrwbB/UjKau+Uj80frui/7x8eU3f3e4XGREW+CSHObUWyuucytzoW5TR0EeS6MX4TJpRhCg4NDzp6vbye3RIN8KAsRSbd8Znh38ABer543rjzB/kn305bKnmbqQtO89pQEidjZX441AYu1dkqTkhLYwHQSxbFNpcE= </D> </RSAKeyValue> ======================== 二.填充算法 由于密钥长度有限,一次性加密的数据长度也有限,因此必须对明文进行分块加密,再合并加密结果。 以1024位密钥为例,n为1024位,即128个字节,则明文需要分块成每块长128个字节,不足128位的使用特定格式的数据填充。 所以分块的算法称为填充算法,有不同的标准,如NoPPadding、OAEPPadding、PKCS1Padding等。 本文实现的是PKCS1Padding填充算法,规范文档是这个:http://man.chinaunix.net/develop/rfc/RFC2313.txt 该算法规定的格式如下: 00 || BT || PS || 00 || D 其中BT可选择01和02,01表示私钥加密,02表示公钥加密。如果BT是01则PS需要使用oxFF填充,为02的话则需要使用随机非0填充。D表示分割的明文块。 PS的长度至少为8个字节,因此D至多只能为128-8-2=117个字节长。 三.C#实现 之所以要使用C#实现是因为官方版本只实现了公钥加密且在明文中间增加了一些数字再进行的加密,使用私钥解密出来后需要去除增加的数字。 而要实现私钥加密则只能自己实现了,这里使用到了一个大整数类:BigInteger。我是从这里下载的:http://www.codeproject.com/Articles/2728/C-BigInteger-Class C#里生成公私钥对可以使用如下代码:
RSACryptoServiceProvider key = new RSACryptoServiceProvider(); RSAParameters param = key.ExportParameters(true); Console.WriteLine(Convert.ToBase64String(param.Modulus)); Console.WriteLine(Convert.ToBase64String(param.Exponent)); Console.WriteLine(Convert.ToBase64String(param.D));
其中Modulus+Exponent为公钥,D为私钥,C#里是以base64格式保存的。 填充算法实现如下(blockLen为模的字节数,这里为128):
//填充 private byte[] add_PKCS1_padding(byte[] oText, int blockLen) { byte[] result = new byte[blockLen]; result[0] = 0x00; result[1] = 0x01; int padLen = blockLen - 3 - oText.Length; for (int i = 0; i < padLen; i++) { result[i + 2] = 0xff; }
result[padLen + 2] = 0x00;
int j = 0; for (int i = padLen + 3; i < blockLen; i++) { result[i] = oText[j++]; } return result; }
私钥加密方法如下:
//私钥加密 private byte[] priEncrypt(byte[] block, RSACryptoServiceProvider key) { RSAParameters param = key.ExportParameters(true); BigInteger d = new BigInteger(param.D); BigInteger n = new BigInteger(param.Modulus); BigInteger biText = new BigInteger(block); BigInteger biEnText = biText.modPow(d, n); return biEnText.getBytes(); }
则整个私钥加密方法可以如下进行:
//私钥加密 public byte[] encryptByPriKey(String src, RSACryptoServiceProvider key){ //获得明文字节数组 byte[] oText = System.Text.Encoding.Default.GetBytes(src); //填充 oText = add_PKCS1_padding(oText, 128); //加密 byte[] result = priEncrypt(oText, key); return result; }
加密结果为字节数组,可以转化为base64编码的字符串,但是建议转化为十六进制字符串,便于用于web传递。 对于大于128字节的明文,需要分成多段进行加密,使用本文的填充算法实现是大于117字节的明文就需要分段。 为了方便的web环境下的数据交换,可以对明文进行urlencode后再进行加密,这样解密后使用urldecode可以得到明文。 实现如下:
//url编码 String urlEncode = System.Uri.EscapeDataString(src);
//以117个字符为长度分割字符串进行加密 int index = 0; int len = urlEncode.Length; String toEncrypt = ""; while (index < len) { String temp = ""; if (index + 117 < len) { temp = urlEncode.Substring(index, index + 117); } else { temp = urlEncode.Substring(index); }
//加密 byte[] encrypted = encryptByPriKey(temp, r);
//转化为16进制字符串 String enc = bytesToHexStr(encrypted); toEncrypt += enc;
index += 117; } Console.WriteLine(toEncrypt);
对节字数组转化为十六进制字符串可以如下实现:
char[] bcdLookup = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
//字节转化为十六进制字符串 public String bytesToHexStr(byte[] bcd) { String s ="";
for (int i = 0; i < bcd.Length; i++) { s +=(bcdLookup[(((byte)bcd[i]) >> 4) & 0x0f]); s+=(bcdLookup[(byte)bcd[i] & 0x0f]); }
return s; }
至此为止,我们便实现了C#的私钥加密算法实现。 C#的签名实现使用官方实现即可与java互通,应该是没有使用填充的原因。 C#签名和验证实现如下:
//明文转化为字节数组 byte[] srcBytes = System.Text.Encoding.Default.GetBytes(src);
//签名 byte[] signed = key.SignData(srcBytes, "sha1");
//转化为16进制字符串 String sign = nr.bytesToHexStr(signed); Console.WriteLine(sign);
//验证 bool verify = r.VerifyData(srcBytes, "sha1", signed); Console.WriteLine(verify);
使用此方法签名的数据可以使用java验证。由于是签名和验证,不需要还原数据,所以可以直接对明文加密,不需要使用urlencode。 |