在对称加密算法中,信息的发送方和接收方用同样的密钥对信息进行加密和解密,而如何安全传递key本身成为了一个十分严重的问题,因此产生了公开密钥密码体制。
公开密钥密码体制使用不同的密钥来进行加密和解密,加密密钥公开(公钥),而解密密钥私有(私钥),加密算法和解密算法都是公开的。解密密钥由加密密钥决定,但却无法从加密密钥运算得到。
由于公钥和加密算法的公开,理论上攻击者可以尝试所有可能的原文并对其加密来与密文进行对比从而找出原文,而实际上这样的计算量过于庞大。所以公开密钥密码体制仅仅是计算上的安全,而非完美的安全。
RSA是目前最有影响力的公钥加密算法,其安全性基于对大素数相乘的结果没有有效的因式分解方法。在实际操作上,只要RSA的密钥足够长的,其加密的信息是无法被破解的。
RSA加密算法的流程
Alice向Bob发送加密信息:
1. Bob产生一对密钥对,包含一个公钥和一个私钥。
2. Bob发布他的公钥
3. Alice用Bob的公钥对信息进行加密并发送给Bob
4. Bob用私钥对收到的加密信息进行解密
RSA数学原理
取两个大素数
p和
q,
m = pq,取
d,
e 使
de = 1 mod (p-1)(q-1)。 加密算法:
ek(x) = xemod m 解密算法:
dk(y) = yd mod m 公钥:
e和
m 密钥:
d和
m
注:通常用
快速幂取模算法进行a^n mod bd的运算。
要证明该算法的正确性,我们只需证明
x = dk(ek(x)),即对信息加密并解密后,能否得到原始信息。 证明: 要证明
x等于
dk(ek(x)) = (xe mod m)d mod m = xde mod pq 因为p和q都是素数,所以我们只需证明
xde
= x mod p 且
xde = x
mod q 即可。
已知de = 1 mod (p-1)(q-1),所以存在h使得de – 1 = h (p -1)(q – 1)
xde = xde-1x = xh(p-1)(q-1)x= (xh(q-1))p-1x
由费马小定理 = 1h(q- 1)x mod p = x
(费马小定理:当x为整数p为素数,xp-1 = 1 mod p)
类似可得 xde = x mod q,得证。
密钥对生成
1. 任选两个大素数p和q(具体做法为随机选择数并验证是否为素数,常用的检验方法有米勒-拉宾素性检验等) 2. 计算m = pq 3. 计算ψ = (p – 1)(q – 1) 4. 选择一个整数e(1<e<ψ) 使 gcd(e,ψ) = 1。实际操作上,e通常在选择p、q之前就确定,经常为65537 5. 计算d =
e
-1mod ψ
6. 发布m 和 e 作为公钥,d保密。
例子
—-Bob产生密钥对 1. Bob选择 p = 5, q = 11 2. 计算m = pq = 55 3. ψ = (p – 1)(q – 1) = 40 4. 选择e = 3,并用扩展欧几里得算法验证gcd(e,ψ) = 1 5. 计算d =
e
-1 mod ψ = 27 6. 发布m = 55和e = 3作为公钥,私钥d = 27 —-Alice想要发送x = 14给Bob 7. Alice用Bob的公钥计算
ek(x) =
14
3 mod 55 = 49 —-Bob接收信息并解密 8. Bob用私钥解密:d
k(x) =
49
27 mod 55 = 14
RSA破解
当RSA没有被正确使用时,它可以非常脆弱。例如对文本加密时,我们将L个字符放到一个整数中进行加密,当L很小时,破解是可行的。
破解的基本思路是用公开的公钥和加密算法对所有可能的值进行进行加密,建立原文和密文的对应表。
分解 c = m^e (mod n) = (m1 * m2)^e (mod n) = (m1^e)*(m2^e) (mod n) = c1*c2 (mod n) ,可以使所需的运算量减少。
破解方法:
已知密文c,公钥(e,n)和参数r(通常r>=k/2),寻找合适的参数r也是破解工作的一部分。
r越大计算量越大破解也越精确,通常当r接近所需的大小时已经可以由人工猜测原文了。
1. 建立表格
for i = 1 to 2^r
table[i] = i^e mod m
2. 找到 i,j < 2^r 使得c * inv(i^e mod n, n) mod m = x,x为 j^e mod n
(y = inv(x,n) 当 (x*y=1) mod n)
3. 如果能在表格中找到这样的x,则原文 = i*j mod m
证明:
我们所寻找的 i,j满足 c*(i^e)^-1 = j^e mod n
(i*j)^e = (i^e)(j^e) = (i^e *c*(i^e)^-1) = c mod n
所以原文为i*j
读取文本文件进行RSA加密的代码
完整项目下载:http://pan.baidu.com/s/13m7rK
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.math.BigInteger;
import java.util.Random;
public class RSA {
final static int L = 3; //size of block - the encryption deals with L characters at a time
final static int PQ_BITLENGTH = 32; //size of p,q
final static int KEY_BITLENGTH = 32; //size of e
static Random rnd = new Random();
static BigInteger big_two = new BigInteger("2");
public static void main(String[] args) {
//System.out.println("test sqm: "+sqm(new BigInteger("3"),new BigInteger("5"),new BigInteger("7")).toString());
//System.out.println("test generatePrime: "+generatePrime(PRIME_BITLENGTH));
long startTime = System.nanoTime();
BigInteger[] keyPair = generateKey();
System.out.println("public key: (" + keyPair[0] + "," + keyPair[1] + ")");
System.out.println("private key: (" + keyPair[2] + "," + keyPair[3] + ")");
//System.out.println("test keyPair: "+sqm(sqm(big_two,keyPair[0],keyPair[1]),keyPair[2],keyPair[3]));
BigInteger[] cipher = encrypt(readFile("rsa"+".plain"),L,keyPair[0],keyPair[1]);
long endTime = System.nanoTime();
System.out.println("Run time: "+(endTime-startTime)+"ns");
writeFile(cipher,keyPair);
}
/*
* Write cipher into file "rsa.crypto"
* Write private key into file "rsa.key"
* Write public key into file "rsa.pub"
*/
static void writeFile(BigInteger[] cipher, BigInteger[] keyPair)
{
try{
//crypto
File crypto = new File("rsa"+".crypto");
crypto.createNewFile();
FileWriter cryptoWriter = new FileWriter(crypto);
for(int i=0;cipher[i]!=null;i++)
cryptoWriter.write(cipher[i].toString()+"\n");
cryptoWriter.close();
//private key
File key = new File("rsa"+".key");
key.createNewFile();
FileWriter keyWriter = new FileWriter(key);
keyWriter.write(keyPair[2].toString()+"\n");
keyWriter.write(keyPair[3].toString()+"\n");
keyWriter.close();
//public key
File pub = new File("rsa"+".pub");
pub.createNewFile();
FileWriter pubWriter = new FileWriter(pub);
pubWriter.write(keyPair[0].toString()+"\n");
pubWriter.write(keyPair[1].toString()+"\n");
pubWriter.close();
}catch(Exception e){
e.printStackTrace();
}
}
//e(k) = x^e mod n where x represent L characters
static BigInteger[] encrypt(int[] plain, int L, BigInteger key_e, BigInteger key_n)
{
BigInteger[] cipher= new BigInteger[100/L+1];
int temp = 0;
int index = 0;
for(int i=0,l=L;plain[i]!=-1;i++)
{
temp = (temp<<8) + plain[i];
if(l==1)
{
cipher[index] = sqm(new BigInteger(""+temp),key_e,key_n);//encrypt
l=L;
index++;
temp = 0;
}
else
l--;
}
return cipher;
}
/*
* Read plain text into an integer array,
* characters are encoded on 8 bits (typically Latin1 encoding)
*/
static int[] readFile(String fileName)
{
int[] plain = new int[102];
for(int i=0;i<plain.length;i++)
plain[i]=-1;
int temp = 0;
int i = 0;
try {
FileReader reader = new FileReader(fileName);
while((temp=reader.read())!=-1)
{
plain[i] = temp;
i++;
}
} catch (Exception e) {
e.printStackTrace();
}
//when the number of characters to encode is not a multiple of L
while(i%L!=0)
{
plain[i]=0;
i++;
}
return plain;
}
/*
* Generate key pair for RSA
* @return
* result[0]&result[1] - public key
* result[2]&result[3] - private key
*/
static BigInteger[] generateKey()
{
BigInteger result[] = new BigInteger[4];
BigInteger p = generatePrime(PQ_BITLENGTH);
BigInteger q,phi,n,e,d;
do{
q = generatePrime(PQ_BITLENGTH);
}while (q.equals(p));
n = p.multiply(q);//n = p*q
phi = (p.subtract(BigInteger.ONE)).multiply((q.subtract(BigInteger.ONE)));//phi = (p-1)*(q-1)
do{
e = generatePrime(KEY_BITLENGTH);
}while(!gcd(e,phi).equals(BigInteger.ONE)||e.compareTo(phi)>=0);
d = inv(e,phi);
result[0]=e;
result[1]=n;
result[2]=d;
result[3]=n;
return result;
}
/*
* Use Extended Euclidean Algorithm to find y = inv(x,n) is such that (x*y=1) mod n
* ax+by=1 return x+b
*/
static BigInteger inv(BigInteger x, BigInteger n)
{
return ext_gcd(x,n)[0].add(n);
}
static BigInteger[] ext_gcd(BigInteger a, BigInteger b)
{
BigInteger [] tempxy = new BigInteger[2];
BigInteger temp = null;
if (b.equals(BigInteger.ZERO))
{
tempxy[0]=BigInteger.ONE;
tempxy[1]=BigInteger.ZERO;
}
else
{
tempxy = ext_gcd(b,a.mod(b));
temp = tempxy[0];
tempxy[0] = tempxy[1];
tempxy[1] = temp.subtract((tempxy[1].multiply(a.divide(b))));
}
return tempxy;
}
//calculate the greatest common divisor of a and b
static BigInteger gcd(BigInteger a, BigInteger b)
{
BigInteger temp;
while(!b.equals(BigInteger.ZERO))
{
temp = b;
b = a.mod(b);
a = temp;
}
return a;
}
//generate a strong probable prime with the specified bitLength
static BigInteger generatePrime(int bitLength){
BigInteger x = new BigInteger(bitLength,rnd);//[0,2^bitLength-1]
//change the most significant bit to 1 to make sure it meets the bitLength requirement
x = x.or(BigInteger.ONE.shiftLeft(bitLength-1));//[2^(bitLength-1),2^bitLength-1]
if(x.mod(big_two).equals(BigInteger.ZERO))//if n is even
x = x.add(BigInteger.ONE);
while(!miller_rabin(x,5))
x = x.add(big_two);
return x;
}
/**
* Use Miller-Rabin to test if n is a strong probable prime
* @param n - number to be tested from primality
* @param k - How many base a to be tested
* @return
* true means n has 1-4^(-k) probability to be prime
* false means n is composite
*/
static boolean miller_rabin(BigInteger n, int k)
{
BigInteger a;
for (;k>0;k--) {
//pick a random integer a in the range [1,N-1]
do{
a = new BigInteger(n.bitLength(), rnd);
}while(a.equals(BigInteger.ZERO)||a.compareTo(n)>=0);
if (!miller_rabin_test(a, n))
return false;
}
return true;
}
//Test if n is a strong probable prime to base a
static boolean miller_rabin_test(BigInteger a, BigInteger n) {
BigInteger n_minus_one = n.subtract(BigInteger.ONE);
BigInteger d = n_minus_one;
int s = d.getLowestSetBit();
d = d.shiftRight(s);
BigInteger x = sqm(a,d,n);
if(x.equals(BigInteger.ONE)||x.equals(n_minus_one))
return true;
for(int i=0;i<s-1;i++)
{
x = x.multiply(x).mod(n);
if(x.equals(BigInteger.ONE))
return false;
if(x.equals(n_minus_one))
return true;
}
return false;
}
//Square-and-Multiply algorithm to calculate (x^e) mod m
static BigInteger sqm(BigInteger x, BigInteger e, BigInteger m){
BigInteger r = new BigInteger("1");
while(e.compareTo(BigInteger.ZERO)>0)
{
if(e.mod(big_two).equals(BigInteger.ONE))//if e is odd
r = r.multiply(x).mod(m);//(r*x) mod m
e = e.divide(big_two);
x = x.multiply(x).mod(m);
}
return r;
}
}