p, q,計算 n = p*q 容易n,分解為 p, q 極難(大整數因子分解問題)n = p * qφ(n) = (p-1)(q-1)b (公鑰指數),滿足 1 < b < φ(n) 且 gcd(b, φ(n)) = 1,常用 65537a (私鑰指數),滿足 a*b ≡ 1 (mod φ(n)),即模逆元(n, b) | 私鑰:(n, a)(p, q 銷毀)C = M^b mod nM = C^a mod npublic class PublicKey { private final BigInteger n, b; /* getter */ }
public class PrivateKey { private final BigInteger n, a; /* getter */ }
public class RsaKeyPair { private final PublicKey publicKey; private final PrivateKey privateKey; /* getter */ }
SecureRandom random = new SecureRandom();
random.setSeed(new Date().getTime());
BigInteger p = BigInteger.probablePrime(bitLength, random);
BigInteger q = BigInteger.probablePrime(bitLength, random);
private static BigInteger x, y;
// 遞歸求解 ax + by = gcd(a,b)
public static BigInteger ex_gcd(BigInteger a, BigInteger b) {
if (b.equals(BigInteger.ZERO)) {
x = BigInteger.ONE; y = BigInteger.ZERO; return a;
}
BigInteger ans = ex_gcd(b, a.mod(b));
BigInteger tmp = x;
x = y;
y = tmp.subtract(a.divide(b).multiply(y));
return ans;
}
// 求 a 對模 k 的乘法逆元
public static BigInteger cal(BigInteger a, BigInteger k) {
BigInteger gcd = ex_gcd(a, k);
if (!BigInteger.ONE.equals(gcd)) return BigInteger.valueOf(-1);
x = x.multiply(BigInteger.ONE.divide(gcd));
k = k.abs();
BigInteger ans = x.mod(k);
return ans.compareTo(BigInteger.ZERO) < 0 ? ans.add(k) : ans;
}
public static RsaKeyPair generatorKey(int bitLength) {
// 生成 p, q, n, φ(n)
// 選擇 b (公鑰指數)
BigInteger a = cal(b, k); // 計算私鑰指數
return new RsaKeyPair(new PublicKey(n, b), new PrivateKey(n, a));
}
public static String encrypt(String src, PublicKey key, String charset) {
byte[] bytes = src.getBytes(charset);
BigInteger m = new BigInteger(1, bytes); // 正數標識
BigInteger c = m.modPow(key.getB(), key.getN());
return Base64.encodeBase64String(c.toByteArray());
}
public static String decrypt(String cipher, PrivateKey key, String charset) {
byte[] bytes = Base64.decodeBase64(cipher);
BigInteger c = new BigInteger(bytes);
BigInteger m = c.modPow(key.getA(), key.getN());
byte[] plain = m.toByteArray();
// 關鍵:BigInteger 以符號位開頭,需去除首字節
plain = Arrays.copyOfRange(plain, 1, plain.length);
return new String(plain, charset);
}
toByteArray() 最高位為符號位,解密時必須 copyOfRange(1, len) 去除,否則中文解密會出現亂碼/前綴空格@Test
public void testRSA() {
RsaKeyPair kp = RSAGeneratorKey.generatorKey(1024);
String src = "哈哈哈哈哈~~~嘿嘿嘿嘿嘿~~---呵呵呵呵呵!";
String enc = RSAUtil.encrypt(src, kp.getPublicKey(), "UTF-8");
String dec = RSAUtil.decrypt(enc, kp.getPrivateKey(), "UTF-8");
assertEquals(src, dec);
}
測試通過,核心流程打通。完整代碼結構見文中截圖。