RSA非对称加密

数学概念

因数
因数是指整数a除以整数b(b≠0) 的商正好是整数而没有余数,我们就说b是a的因数。
a × b = c
a和b是c的因数

质数(素数)
质数是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。
5 = 1 × 5
5就是质数

余数
余数指整数除法中被除数未被除尽部分,且余数的取值范围为0到除数之间(不包括除数)的整数。
3 % 2 = 1
1就是余数

加解密过程

在这里插入图片描述
明文:3,1,15
公钥:(7,33
私钥:(3,33
注:数字33为公私钥共有

生成公私钥

在这里插入图片描述

  1. 随机选两个质数(两个质数越大,就越难破解)
  2. 将两个质数相乘,得到N(N的长度就是密钥长度。3233写成二进制是110010100001,一共有12位,所以这个密钥就是12位。实际应用中,RSA密钥一般是1024位,重要场合则为2048位)
  3. 根据欧拉函数,将两个质数各自减1后相乘,得到T(欧拉函数性质3)
  4. 选取公钥:公钥必须是质数;公钥必须大于1小于T;不是T的因子
  5. 算私钥:D和E相乘后,除以T的余数必须等于1

欧拉函数

定义
欧拉函数是小于n的数中与n互质的数的数目。例如φ(8)=4,因为1,3,5,7均和8互质。

性质
在这里插入图片描述
注意:每种质因数只一个。比如12=223那么φ(12)=12*(1-1/2)*(1-1/3)=4

性质1和2是显而易见的,性质3和4的证明省略,有兴趣的伙伴可以去研究研究。

到目前为止,我们已经对欧拉函数有了一定的了解了,下面来看看著名的欧拉定理

欧拉定理

欧拉函数的用处,在于欧拉定理。

欧拉定理是指:
如果两个正整数a和n互质,则n的欧拉函数 φ(n) 可以让下面的等式成立:
a ϕ ( n ) ≡ 1 ( m o d n ) a^{\phi(n)} \equiv 1 \quad (mod \quad n) aϕ(n)1(modn)

假设正整数a与质数p互质,因为质数p的φ§等于p-1,则欧拉定理可以写成
a p − 1 ≡ 1 ( m o d p ) a^{p - 1} \equiv 1 \quad (mod \quad p) ap11(modp)
这就是著名的费马小定理,它是欧拉定理的特例。

到目前为止,RSA算法还需要最后一个概念:模反元素

模反元素

如果两个正整数a和n互质,那么一定可以找到整数b,使得ab-1被n整除,或者说ab被n除的余数是1。
a b ≡ 1 ( m o d n ) ab \equiv 1 \quad (mod \quad n) ab1(modn)
此条定理可以用欧拉定理证明,令 b = a ϕ ( n ) − 1 b = a^{\phi(n)-1} b=aϕ(n)1即可。

使用事项

密钥长度

首先我们说的“密钥”是指谁?由于RSA密钥是(公钥e+模值n)、(私钥d+模值n)分组分发的,单独给对方一个公钥或私钥是没有任何用处,所以我们说的“密钥”其实是它们两者中的其中一组。但我们说的“密钥长度”一般只是指模值的位长度。目前主流可选值:1024、2048、3072、4096…

模值主流长度是多少?
目前主流密钥长度至少都是1024bits以上,低于1024bit的密钥已经不建议使用(安全问题)。那么上限在哪里?没有上限,多大都可以使用。所以,主流的模值是1024位,实际运算结果可能会略小于1024bits,注意,这个值不是绝对的,跟素数的生成算法有关系,只是告诉素数生成器“帮我生成一个接近1024位的素数而已”,然后生成器“好,给您一个,这个差不多1024位”。

素数生成器这么厉害?说生成1024位就会出个1024位的大整数?真实的情况是素数生成器也只是在1024bits对应的整数附近进行“摸索”而已,大家其实都不容易,又要快又要准确又要随机性,那么素数生成器也只能应付一下,找到1024位的算是好运,没找到1024位,1023位也照样送出来。

公钥指数如何确定?
公钥指数是随意选的,但目前行业上公钥指数普遍选的都是65537(0x10001,5bits),该值是除了1、3、5、17、257之外的最小素数,为什么不选的大一点?当然可以,只是考虑到既要满足相对安全、又想运算的快一点(加密时),PKCS#1的一个建议值而已。

有意的把公钥指数选的小一点,但是对应私钥指数肯定很大,意图也很明确,大家都要用公钥加密,所以大家时间很宝贵,需要快一点,您一个人私钥解密,时间长一点就多担待,少数服从多数的典型应用。

私钥指数如何确定?
公钥指数随意选,那么私钥就不能再随意选了,只能根据算法公式(ed%k=1,k=(p-1)(q-1))进行运算出来。那么私钥指数会是多少位?根据ed关系,私钥d=(x*k+1)/e,所以单看这个公式,私钥指数似乎也不是唯一结果,可能大于也可能小于1024bits的,但我们习惯上也是指某个小于1024bits的大整数。

包括前文的公钥指数,在实际运算和存储时为方便一般都是按照标准位长进行使用,前面不足部分补0填充,所以,使用保存和转换这些密钥需要注意统一缓冲区的长度。

明文长度

网上有说明文长度小于等于密钥长度(Bytes)-11,这说法本身不太准确,会给人感觉RSA 1024只能加密117字节长度明文。实际上,RSA算法本身要求加密内容也就是明文长度m必须0<m<模长n,也就是说内容这个大整数不能超过n,否则就出错。那么如果m=0是什么结果?普遍RSA加密器会直接返回全0结果。如果m>n,运算就会出错?!那怎么办?且听下文分解。

所以,RSA实际可加密的明文长度最大也是1024bits,但问题就来了:
如果小于这个长度怎么办?就需要进行padding,因为如果没有padding,用户无法确定解密后内容的真实长度,字符串之类的内容问题还不大,以0作为结束符,但对二进制数据就很难理解,因为不确定后面的0是内容还是内容结束符。

只要用到padding,那么就要占用实际的明文长度,于是才有117字节的说法。我们一般使用的padding标准有NoPPadding、OAEPPadding、PKCS1Padding等,其中PKCS#1建议的padding就占用了11个字节。

如果大于这个长度怎么办?很多算法的padding往往是在后边的,但PKCS的padding则是在前面的,此为有意设计,有意的把第一个字节置0以确保m的值小于n。

这样,128字节(1024bits)-减去11字节正好是117字节,但对于RSA加密来讲,padding也是参与加密的,所以,依然按照1024bits去理解,但实际的明文只有117字节了。

我们在把明文送给RSA加密器前,要确认这个值是不是大于n,也就是如果接近n位长,那么需要先padding再分段加密。除非我们是“定长定量自己可控可理解”的加密不需要padding。

密文长度

密文长度就是给定符合条件的明文加密出来的结果位长,这个可以确定,加密后的密文位长跟密钥的位长度是相同的,因为加密公式:
C=(P^e)%n

所以,C最大值就是n-1,所以不可能超过n的位数。尽管可能小于n的位数,但从传输和存储角度,仍然是按照标准位长来进行的,所以,即使我们加密一字节的明文,运算出来的结果也要按照标准位长来使用(当然了,除非我们能再采取措施区分真实的位长,一般不在考虑)。

至于明文分片多次加密,自然密文长度成倍增长,但已不属于一次加密的问题,不能放到一起考虑。

填充方式

RSA加密常用的填充模式有三种:
RSA_PKCS1_PADDING
RSA_PKCS1_OAEP_PADDING(最安全)
RSA_NO_PADDING

在这里插入图片描述
在这里插入图片描述

代码示例

//生成公钥私钥
@Test
public void testInitRsa() throws NoSuchAlgorithmException {
    KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
    // 初始化密钥对生成器
    keyPairGen.initialize(1024, new SecureRandom());
    // 生成一个密钥对,保存在keyPair中
    KeyPair keyPair = keyPairGen.generateKeyPair();
    // 得到私钥
    RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
    // 得到公钥
    RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
    String publicKey = Base64.getEncoder().encodeToString(rsaPublicKey.getEncoded());
    // 得到私钥字符串
    String privateKey = Base64.getEncoder().encodeToString(rsaPrivateKey.getEncoded());

    System.out.println("公钥:");
    System.out.println(publicKey);
    System.out.println("私钥:");
    System.out.println(privateKey);
}


String publicKeyStr = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCNgRwZfysyqtUV38uRMAnacaPL9ikPw1l1Qz9ZPSiKTh+H3fdAkMnHoWpfi1BGbrGoe+cinJYE4Urv116H/2CTqA46RNI3gXdPiaxL1nIaGfhPmvrfo/Ephh5qttfZY1VzU7u6G4xHxGlbMeTRY5j2E1BpMZ1n1r5AZk4ipZ1WfQIDAQAB";
String privateKeyStr = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAI2BHBl/KzKq1RXfy5EwCdpxo8v2KQ/DWXVDP1k9KIpOH4fd90CQycehal+LUEZusah75yKclgThSu/XXof/YJOoDjpE0jeBd0+JrEvWchoZ+E+a+t+j8SmGHmq219ljVXNTu7objEfEaVsx5NFjmPYTUGkxnWfWvkBmTiKlnVZ9AgMBAAECgYARxaBo117uiyQeDjB7+KOUKueeizkXALrwjlSbeaHo1O0C3G7cThL5np1EiLO5VNe9SGp8H1x8GmuV+d+mhv1y/hmifCEe7lRcjbjj1yY5deC+6d7s0duNBELBNDYQGzZFJLMOZOfDFTux1b+cb7u/XIoA4vmSMsYqDf+QNDqCQQJBAM2m9Ycws2N65/kpu+WYuMqpeiOVF5YlgHlu/Ef6C5J6SA6E69QeFwcpSWDed0SxYhOkRoOMup2zkWagg43iCu0CQQCwJb/pZ1KOyJelPb9OzlnfjWLmGCkhOQTGZusXNn8ufgnuF2PRfK8rM9grvDr6GvSfnkgoj7j/7WQRsrdD77fRAkBaojFlUlZNCna/vd/ccTFHx+dXVTbe6kozunEe+5ksBg5Jr9ULV/OIKW74bTMdKBv0UhrtkRERvXjgRO+VkUCFAkBuX2kOLkDL2SM0VimOjrw/2M70kIUNHTZBv3FlfS9aCX3dY2iXdkIolvbICM0HtGY9sidSyyPHsY83i71L42RRAkEAvpkp8JJc3Jkq/mKP2XnW9i8/QvncGaB8NG88OOeYu/XuxF/9X1Gx6UCspnwNbxDCWXccHQOKW2D4EUudhPZsEw==";

@Test
public void testRsaEncode() throws Exception {
//        Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPADDING");
//        Cipher cipher = Cipher.getInstance("RSA/ECB/NOPADDING");
    Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKeyStr));
    PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);

    cipher.init(Cipher.ENCRYPT_MODE, publicKey);
    System.out.println(Base64.getEncoder().encodeToString(cipher.doFinal("hello world".getBytes())));
}

@Test
public void testRsaDecode() throws Exception {
//        Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPADDING");
//        Cipher cipher = Cipher.getInstance("RSA/ECB/NOPADDING");
    Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKeyStr));
    PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);

    cipher.init(Cipher.DECRYPT_MODE, privateKey);
    String data = "KW4Y+ztnkwzdclDRhQ0EzN4mYwGE6lfcsV+1BmsECHQURIHCZLN/3rMWS9XXhvfNpj4Pl5pGJJTywGlQnNpL+nvIMFfLPrkbzZ75rI+osni8K7SxrKJJjscMFfPbf7hbOqs6+W8zJ6qYYgsodEjbm2/xTNK62a9kJ70s8jSv9Ac=";
    System.out.println(new String(cipher.doFinal(Base64.getDecoder().decode(data))));
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值