2

前言

最近一场面试,面试官问了我
对称加密与非对称加密的问题,虽然曾经看过一些内容,但是没有系统的整理,所以当被问的时候,脑子里一片空白,没有回答上来。因此,在这里重新梳理一下密码学的知识点,夯实一下基础。

killBase系列 -- 密码学(二) 传送门

密码学

一、基础

  1. 密码学算法分类:

    • 消息编码:Base64

    • 消息摘要:MD类, SHA类,MAC

    • 对称密码:DES,3DES,AES

    • 非对称密码:RSA,DH

    • 数字签名:RSASignature,DSASignature

  2. 五元组

1)明文:原始信息。
2)加密算法:以密钥为参数,对明文进行多种置换和转换的规则和步骤,变换结果为密文。
3)解密算法:加密算法的逆变换,以密文为输入、密钥为参数,变换结果为明文。:
4)密钥:加密与解密算法的参数,直接影响对明文进行变换的结果。
5)密文:对明文进行变换的结果。

  1. Java编程中常用类 -- java.security 包

    1. 消息编码:BASE64Encoder,BASE64Decoder -- java.util

    2. 消息摘要:MessageDigest

    3. 对称密码:KeyGenerator,SeretkeyFactory -- javax.crypto 包(提供给AES,DES,3DES,MD5,SHA1等 对称 和 单向加密算法。),Cipher

    4. 非对称密码:KeyPairGenerator,KeyFactory -- java.security 包(提供给DSA,RSA, EC等 非对称加密算法。),KeyPair,PublicKey,PrivateKey,Cipher

    5. 数字重命名:Signature

  2. 常用开源工具

    • Commons.Codec

    • Bouncy.Castle

二、Base64 算法

  1. Base64 基于64个字符编码算法,以任意 8 位字节序列组合描述形式 , BASE加密后产生的字节位数是8的倍数,如果不够位数以=符号填充。对此 Base64 算法有一套字符映射表。

  2. 使用方法:

// 获取
    Base64.Encoder encoder = Base64.getEncoder();
    Base64.Decoder decoder = Base64.getDecoder();
// 加密
    public byte[] encode(byte[] src);
    * @param   src
    *          the byte array to encode
    * @param   dst
    *          the output byte array
    * @return  The number of bytes written to the output byte array
    public int encode(byte[] src,byte[] dst);
    public String encodeToString(byte[] src);
    public ByteBuffer encode(ButeBuffer buffer);
// 解密
    public byte[] decode(byte[] src);
    * @param   src
    *          the byte array to encode
    * @param   dst
    *          the output byte array
    * @return  The number of bytes written to the output byte array
    public int decode(byte[] src,byte[] dst);
    public byte[] decode(String src);
    public ByteBuffer decode(ButeBuffer buffer);

三、消息摘要

  1. 介绍:又称为 哈希算法。唯一对应一个消息或文体固定长度值,由一个单向的Hash加密函数对消息进行作用而产生。

  2. 分类: MD(Message Digest) 消息摘要算法,SHA(Secure Hash Algorithm) 安全散列算法, MAC(Message Authentication Code):消息认证算法

  3. 主要方法:

// xxx 可以为 md5,sha
MessageDigest.getInstance("xxx")
1. MD5算法

原理:
首先需要对信息进行填充,使其位长对512求余的结果等于448。
因此,信息的位长(Bits Length)将被扩展至N*512+448,N为一个非负整数,N可以是零。
填充的方法如下,在信息的后面填充一个1和无数个0,直到满足上面的条件时才停止用0对信息的填充。
然后,在这个结果后面附加一个以64位二进制表示的填充前信息长度。
经过这两步的处理,信息的位长=N512+448+64=(N+1)512,即长度恰好是512的整数倍
MD5以512位分组来处理输入的信息,且每一分组又被划分为16个32位子分组,经过了一系列的处理后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值。

代码实现

public class MD5Util {
    /*** 
     * MD5加密 生成32位md5码
     * @param 待加密字符串
     * @return 返回32位md5码
     */
    public static String md5Encode(String inStr) throws Exception {
        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (Exception e) {
            System.out.println(e.toString());
            e.printStackTrace();
            return "";
        }

        byte[] byteArray = inStr.getBytes("UTF-8");
        byte[] md5Bytes = md5.digest(byteArray);
        StringBuffer hexValue = new StringBuffer();
        // 转化为 16 进制
        // 原理 : byte 为 8 字节。 0xff --> 11111111
        // byte&0xff 如果小于16 则小于00010000 
        // 所以由 toHexString() 只能转化为 1 位,所以要在前面加上 ‘0’。再加上实际的值。
        for (int i = 0; i < md5Bytes.length; i++) {
            int val = ((int) md5Bytes[i]) & 0xff;
            if (val < 16) {
                hexValue.append("0");
            }
            hexValue.append(Integer.toHexString(val));
        }
        return hexValue.toString();
    }
}
2. SHA 算法

原理:接收一段明文,然后以一种不可逆的方式将它转换成一段(通常更小)密文,也可以简单的理解为取一串输入码(称为预映射或信息),并把它们转化为长度较短、位数固定的输出序列即散列值(也称为信息摘要或信息认证代码)的过程。

特点:该算法输入报文的长度不限,产生的输出是一个160位的报文摘要。输入是按 512 位的分组进行处理的。

作用:通过散列算法可实现数字签名实现,数字签名的原理是将要传送的明文通过一种函数运算(Hash)转换成报文摘要(不同的明文对应不同的报文摘要),报文摘要加密后与明文一起传送给接受方,接受方将接受的明文产生新的报文摘要与发送方的发来报文摘要解密比较,比较结果一致表示明文未被改动,如果不一致表示明文已被篡改。

代码实现

public class SHAUtil {
    /*** 
     * SHA加密 生成40位SHA码
     * @param 待加密字符串
     * @return 返回40位SHA码
     */
    public static String shaEncode(String inStr) throws Exception {
        MessageDigest sha = null;
        try {
            sha = MessageDigest.getInstance("SHA");
        } catch (Exception e) {
            System.out.println(e.toString());
            e.printStackTrace();
            return "";
        }

        byte[] byteArray = inStr.getBytes("UTF-8");
        byte[] md5Bytes = sha.digest(byteArray);
        StringBuffer hexValue = new StringBuffer();
        for (int i = 0; i < md5Bytes.length; i++) {
            int val = ((int) md5Bytes[i]) & 0xff;
            if (val < 16) { 
                hexValue.append("0");
            }
            hexValue.append(Integer.toHexString(val));
        }
        return hexValue.toString();
    }
3. HMAC 算法

原理:用公开函数和密钥产生一个固定长度的值作为认证标识,用这个 标识鉴别消息的完整性。使用一个密钥生成一个固定大小的小数据块,即MAC,并将其加入到消息中,然后传输。接收方利用与发送方共享的密钥进行鉴别认证 等。

代码实现

    // 构建密钥
    public static byte[] getSecretKey(){
            // 初始化
            KeyGenerator keyGen = null;
            try {
                    keyGen = KeyGenerator.getInstance("HmacMD5");
            } catch (NoSuchAlgorithmException e1) {
                    e1.printStackTrace();
            }
            // 产生密钥
            SecretKey secretKey1 = keyGen.generateKey();
            // 得到密钥字节数组
            byte[] key = secretKey1.getEncoded();
            return key;
    }
    // 执行消息摘要
    public static void doHMAC(byte[] data,String key){
            // 从字节数组还原
            SecretKey secretKey2 = new SecretKeySpec(key,"HmacMD5");
            try {
                    // 实例化 Mac
                    Mac mac = Mac.getInstance("HmacMD5");
                    // 密钥初始化 Mac
                    mac.init(secretKey2);
                    // 执行消息摘要
                    byte[] result = mac.doFinal(data);
            } catch (InvalidKeyException e) {
                    e.printStackTrace();
            } catch (NoSuchAlgorithmException e) {
                    e.printStackTrace();
            }
    }
4. SHA 与 MD5比较

1)对强行攻击的安全性:最显著和最重要的区别是SHA-1摘要比MD5摘要长32 位。使用强行技术,产生任何一个报文使其摘要等于给定报摘要的难度对MD5是2^128数量级的操作,而对SHA-1则是2^160数量级的操作。这样,SHA-1对强行攻击有更大的强度。
2)对密码分析的安全性:由于MD5的设计,易受密码分析的攻击,SHA-1显得不易受这样的攻击。
3)速度:在相同的硬件上,SHA-1的运行速度比MD5慢。

四、对称加密

  1. 定义:在对称加密算法中,数据发信方将明文(原始数据)和加密密钥(mi yue)一起经过特殊加密算法处理后,使其变成复杂的加密密文发送出去。

收信方收到密文后,若想解读原文,则需要使用加密用过的密钥相同算法逆算法对密文进行解密,才能使其恢复成可读明文。
在对称加密算法中,使用的密钥只有一个,发收信双方都使用这个密钥对数据进行加密和解密,这就要求解密方事先必须知道加密密钥。

  1. 优缺点

    • 优点:算法公开、计算量小、加密速度快、加密效率高。

    • 缺点:
      (1)交易双方都使用同样钥匙,安全性得不到保证。
      (2)每对用户每次使用对称加密算法时,都需要使用其他人不知道的惟一钥匙,这会使得发收信双方所拥有的钥匙数量呈几何级数增长,
      密钥管理成为用户的负担。对称加密算法在分布式网络系统上使用较为困难,主要是因为密钥管理困难,使用成本较高。

  2. 常用的对称加密算法。

DES(Data Encryption Standard):数据加密标准,速度较快,适用于加密大量数据的场合。
3DES(Triple DES):是基于DES,对一块数据用三个不同的密钥进行三次加密,强度更高。
AES(Advanced Encryption Standard):高级加密标准,是下一代的加密算法标准,速度快,安全级别最高

  1. 对称密码常用的数学运算

    • 移位和循环移位

  移位就是将一段数码按照规定的位数整体性地左移或右移。循环右移就是当右移时,把数码的最后的位移到数码的最前头,循环左移正相反。例如,对十进制数码12345678循环右移1位(十进制位)的结果为81234567,而循环左移1位的结果则为23456781。

  • 置换

  就是将数码中的某一位的值根据置换表的规定,用另一位代替。它不像移位操作那样整齐有序,看上去杂乱无章。这正是加密所需,被经常应用。

  • 扩展

  就是将一段数码扩展成比原来位数更长的数码。扩展方法有多种,例如,可以用置换的方法,以扩展置换表来规定扩展后的数码每一位的替代值。

  • 压缩

  就是将一段数码压缩成比原来位数更短的数码。压缩方法有多种,例如,也可以用置换的方法,以表来规定压缩后的数码每一位的替代值。

  • 异或

  这是一种二进制布尔代数运算。异或的数学符号为⊕ ,它的运算法则如下:

1⊕ 1 = 0 
0⊕ 0 = 0 
1⊕ 0 = 1 
0⊕ 1 = 1 

  也可以简单地理解为,参与异或运算的两数位如相等,则结果为0,不等则为1。

  • 迭代

  迭代就是多次重复相同的运算,这在密码算法中经常使用,以使得形成的密文更加难以破解。

  1. 分组加密

参考 分组加密的四种模式
ECB模式 -- 电子密码本模式
CBC模式 -- 密码分组链接模式
CFB模式 -- 密文反馈模式
OFB模式 -- 输出反馈模式
CTR模式 -- 计数器模式

  1. 常用的填充方式

在Java进行DES、3DES和AES三种对称加密算法时,常采用的是NoPadding(不填充)、Zeros填充(0填充)、PKCS5Padding填充。

  • ZerosPadding

    全部填充为0的字节,结果如下:
    F1 F2 F3 F4 F5 F6 F7 F8 //第一块
    F9 00 00 00 00 00 00 00 //第二块

  • PKCS5Padding

    每个填充的字节都记录了填充的总字节数,结果如下:
    F1 F2 F3 F4 F5 F6 F7 F8 //第一块
    F9 07 07 07 07 07 07 07 //第二块

注: 如果

1. DES(Data Encryption Standard)
1、 介绍:

DES算法的入口参数有三个:Key、Data、Mode
Key为8个字节共64位,其中密钥 56 位,校验位 8 位(每组的 第8位都被用作奇偶校验),是DES算法的工作密钥;
Data也为8个字节64位,是要被加密或被解密的数据;
Mode为DES的工作方式,有两种:加密或解密。

2、 加密过程:

简略版:

  • 首先要生成一套加密密钥,从用户处取得一个64位长的密码口令,然后通过等分、移位、选取和迭代形成一套16个加密密钥,分别供每一轮运算中使用。
    过程 1,2

  • DES对64位(bit)的明文分组M进行操作,M经过一个初始置换IP,置换成m0。将m0明文分成左半部分和右半部分m0 = (L0,R0),各32位长。然后进行16轮完全相同的运算(迭代),这些运算被称为函数f,在每一轮运算过程中数据与相应的密钥结合。
    过程 4

  • 在每一轮中,密钥位移位,然后再从密钥的56位中选出48位。通过一个扩展置换将数据的右半部分扩展成48位,并通过一个异或操作替代成新的48位数据,再将其压缩置换成32位。这四步运算构成了函数f。然后,通过另一个异或运算,函数f的输出与左半部分结合,其结果成为新的右半部分,原来的右半部分成为新的左半部分。将该操作重复16次。
    过程 3 ,5 ,6 ,7 , 8 , 9

  • 经过16轮迭代后,左,右半部分合在一起经过一个逆置换(数据整理),恢复原先的顺序,这样就完成了加密过程。
    过程 10.

详细版请见 附录

3、 解密过程

  加密和解密使用相同的算法!
  DES加密和解密唯一的不同是密钥的次序相反。如果各轮加密密钥分别是K1,K2,K3…K16,那么解密密钥就是K16,K15,K14…K1。这也就是DES被称为对称算法的理由吧。

4、流程如图:

5、注意:

DES算法中只用到64位密钥中的其中56位,而第8、16、24、......64位8个位并未参与DES运算

6、3DES

3DES(或称为Triple DES)

原理:

使用3条56位的密钥对 数据进行三次加密。
7、Java 实现

相关的类

// 生成密钥
KeyGenerator,SecretKeyFactory
// 密钥
SecretKey , SecretKeySpec
// 密码
Cipher 

这里重点讲一下 Cipher 类

  1. 首先要设置参数

Cipher.getInstance(加解密算法,加解密模式,填充模式)

  1. 初始化

Cipher.init(加解密模式 -- Cypher.ENCRIPT/DECRYPT,密钥)

  1. 完成加解密

Cipher.doFinal(bytes) -- 将bytes 内容 加密/解密 然后返回。

这里使用 SecretKeyFactory的密钥 选择CBC模式 进行加解密。

public class DESCryptography {  
  
    public static void main(String[] args) {  
        // TODO Auto-generated method stub  
          
        String content="aaaaaaaabbbbbbbbaaaaaaaa";  
        String key="01234567";  
      
        System.out.println("加密前:"+byteToHexString(content.getBytes()));  
        byte[] encrypted=DES_CBC_Encrypt(content.getBytes(), key.getBytes());  
        System.out.println("加密后:"+byteToHexString(encrypted));  
        byte[] decrypted=DES_CBC_Decrypt(encrypted, key.getBytes());  
        System.out.println("解密后:"+byteToHexString(decrypted));  
    }  
  
    public static byte[] DES_CBC_Encrypt(byte[] content, byte[] keyBytes){        
        try {  
            DESKeySpec keySpec=new DESKeySpec(keyBytes);  
            SecretKeyFactory keyFactory=SecretKeyFactory.getInstance("DES");              
            SecretKey key=keyFactory.generateSecret(keySpec);         
              
            Cipher cipher=Cipher.getInstance("DES/CBC/PKCS5Padding");  
            cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(keySpec.getKey()));             
            byte[] result=cipher.doFinal(content);  
            return result;  
        } catch (Exception e) {  
            // TODO Auto-generated catch block  
            System.out.println("exception:"+e.toString());  
        }  
        return null;  
    }  
      
    public static byte[] DES_CBC_Decrypt(byte[] content, byte[] keyBytes){        
        try {  
            DESKeySpec keySpec=new DESKeySpec(keyBytes);  
            SecretKeyFactory keyFactory=SecretKeyFactory.getInstance("DES");  
            SecretKey key=keyFactory.generateSecret(keySpec);  
              
            Cipher cipher=Cipher.getInstance("DES/CBC/PKCS5Padding");  
            cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(keyBytes));  
            byte[] result=cipher.doFinal(content);  
            return result;  
        } catch (Exception e) {  
            // TODO Auto-generated catch block  
            System.out.println("exception:"+e.toString());  
        }  
        return null;  
    }  
      
    public static String byteToHexString(byte[] bytes) {  
        StringBuffer sb = new StringBuffer(bytes.length);  
        String sTemp;  
        for (int i = 0; i < bytes.length; i++) {  
            sTemp = Integer.toHexString(0xFF & bytes[i]);  
            if (sTemp.length() < 2)  
                sb.append(0);  
            sb.append(sTemp.toUpperCase());  
        }  
        return sb.toString();  
    }  
        
     private static byte toByte(char c) {  
        byte b = (byte) "0123456789ABCDEF".indexOf(c);  
        return b;  
     }  
}  
2. AES(Advanced Encryption Standard)

有时间 再写。。。 看了一天的 加密 ,累死。。。

五、非对称加密

1. 基础

定义:需要两个密钥,一个是公开密钥,另一个是私有密钥;一个用作加密的时候,另一个则用作解密。
使用其中一个密钥把明文加密后所得的密文,只能用相对应的另一个密钥才能解密得到原本的明文;甚至连最初用来加密的密钥也不能用作解密。
由于加密和解密需要两个不同的密钥,故被称为非对称加密

数论知识:

非对称加密运用了一部分数论知识,有兴趣的自己去看下。。。 这里提供一下链接。
阮一峰大神写了一部分,可以帮助理解

一、互质关系:

如果两个正整数,除了1以外,没有其他公因子,我们就称这两个数是互质关系(coprime)。比如,15和32没有公因子,所以它们是互质关系。这说明,不是质数也可以构成互质关系。
二、欧拉函数
三、欧拉定理)
四、模反元素(模逆元)
五、扩展欧几里得算法

2. RSA 算法
2.1 过程
  1. 随机选择两个不相等的质数 p 和 q

    1. = 61, q = 53

  2. 计算 p 和 q 的乘积 n

    1. = 61*53 = 3233

  3. 计算 n 的欧拉函数 φ(n)
    φ(n) = (p-1)(q-1) = 60 * 52 = 3120

  4. 随机选择一个整数 e , 条件是 1 < e < φ(n) , 且 e 与 φ(n) 互质

    1. = 17 ( 实际应用中,常常选择 65537 )

  5. 计算 e 对于 φ(n) 的模反元素 d

    所谓"模反元素"就是指有一个整数d,可以使得ed被φ(n)除的余数为1。
      ed ≡ 1 (mod φ(n))

  ed - 1 = kφ(n)

于是,找到模反元素d,实质上就是对下面这个二元一次方程求解。
  ex + φ(n)y = 1
已知 e=17, φ(n)=3120,
  17x + 3120y = 1
这个方程可以用"扩展欧几里得算法"求解,此处省略具体过程。总之,爱丽丝算出一组整数解为 (x,y)=(2753,-15),即 d=2753。
至此所有计算完成。 
6. 将 n 和 e 封装成公钥, n 和 d 封装成私钥
 公钥 (3233,17), 私钥 (3233,2753)
7. 加密与解密
 - 加密用 (n , e)
     加密信息 -- **明文**为 m , **m 小于 n**
     $m^e$ ≡ c (mod n) 
     公钥是 (3233,17), m 假设为 65
     $65^{17}$ ≡ 2790(mod 3233)
     所以 c = 2790
 - 解密用 (n , d)
     **密文** 为 c
     $c^d$ ≡ m(mod n)
     $2790^{2753}$ ≡ 65 (mod 3233)
     所以 m = 65
8. 私钥解密的证明 -- 有兴趣的同学自己去找资料看下,也是数论的知识。        

##### 2.2 RSA 算法的可靠性 与 破解
以上密钥的生成步骤,出现了六个数字
>  p, q, n, φ(n), e, d
公钥为 n, e 
如果想要得到 d,需要进行以下逆推

  (1)ed≡1 (mod φ(n))。只有知道e和φ(n),才能算出d。
  (2)φ(n)=(p-1)(q-1)。只有知道p和q,才能算出φ(n)。
  (3)n=pq。只有将n因数分解,才能算出p和q。

所以 如果将 n 进行 **因数分解**,就意味着私钥被破解。 可是,大整数的因数分解,是一件非常困难的事情。目前,除了暴力破解,还没有发现别的有效方法。

** 注意:**这里说大整数,不是 像上文 3233 这样的数字,历史上最大的已经进行因数分解的整数为 

  12301866845301177551304949
  58384962720772853569595334
  79219732245215172640050726
  36575187452021997864693899
  56474942774063845925192557
  32630345373154826850791702
  61221429134616704292143116
  02221240479274737794080665
  351419597459856902143413

它等于这样两个质数的乘积

  33478071698956898786044169
  84821269081770479498371376
  85689124313889828837938780
  02287614711652531743087737
  814467999489
    ×
  36746043666799590428244633
  79962795263227915816434308
  76426760322838157396665112
  79233373417143396810270092
  798736308917


**破解:** 这里有一篇关于 RSA 破解的文章,有兴趣的同学可以看一下。
[RSA计时攻击](https://juejin.im/post/5937e8252f301e006b2c4e84)

##### 2.3 Java 实现
**使用到的类**: java.security

// 生成 公钥,密钥
KeyPairGenerator --> KeyPair , KeyFactory --> RSA XXX Spec
// 公钥 密钥
KeyPair
RSAPublicKeySpec --> RSAPublicKey
RSAPrivateKeySpec --> RSAPrivateKey
// 密码
Cipher -- 1.Cipher.getInstance("RSA")

        2.init(mode, key)
        3.cipher.doFinal() 

public static void main(String[] args) throws Exception {

    // TODO Auto-generated method stub  
    HashMap<String, Object> map = RSAUtils.getKeys();  
    //生成公钥和私钥  
    RSAPublicKey publicKey = (RSAPublicKey) map.get("public");  
    RSAPrivateKey privateKey = (RSAPrivateKey) map.get("private");  
      
    //模  
    String modulus = publicKey.getModulus().toString();  
    //公钥指数  
    String public_exponent = publicKey.getPublicExponent().toString();  
    //私钥指数  
    String private_exponent = privateKey.getPrivateExponent().toString();  
    //明文  
    String ming = "123456789";  
    //使用模和指数生成公钥和私钥  
    RSAPublicKey pubKey = RSAUtils.getPublicKey(modulus, public_exponent);  
    RSAPrivateKey priKey = RSAUtils.getPrivateKey(modulus, private_exponent);  
    //加密后的密文  
    String mi = RSAUtils.encryptByPublicKey(ming, pubKey);  
    System.err.println(mi);  
    //解密后的明文  
    ming = RSAUtils.decryptByPrivateKey(mi, priKey);  
    System.err.println(ming);  
}  



**RSAUtils.java**

public class RSAUtils {

/** 
 * 生成公钥和私钥 
 * @throws NoSuchAlgorithmException  
 * 
 */  
public static HashMap<String, Object> getKeys() throws NoSuchAlgorithmException{  
    HashMap<String, Object> map = new HashMap<String, Object>();  
    KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");  
    keyPairGen.initialize(1024);  
    KeyPair keyPair = keyPairGen.generateKeyPair();  
    RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();  
    RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();  
    map.put("public", publicKey);  
    map.put("private", privateKey);  
    return map;  
}  
/** 
 * 使用模和指数生成RSA公钥 
 * 注意:【此代码用了默认补位方式,为RSA/None/PKCS1Padding,不同JDK默认的补位方式可能不同,如Android默认是RSA 
 * /None/NoPadding】 
 *  
 * @param modulus 
 *            模 
 * @param exponent 
 *            指数 
 * @return 
 */  
public static RSAPublicKey getPublicKey(String modulus, String exponent) {  
    try {  
        BigInteger b1 = new BigInteger(modulus);  
        BigInteger b2 = new BigInteger(exponent);  
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");  
        RSAPublicKeySpec keySpec = new RSAPublicKeySpec(b1, b2);  
        return (RSAPublicKey) keyFactory.generatePublic(keySpec);  
    } catch (Exception e) {  
        e.printStackTrace();  
        return null;  
    }  
}  

/** 
 * 使用模和指数生成RSA私钥 
 * 注意:【此代码用了默认补位方式,为RSA/None/PKCS1Padding,不同JDK默认的补位方式可能不同,如Android默认是RSA 
 * /None/NoPadding】 
 *  
 * @param modulus 
 *            模 
 * @param exponent 
 *            指数 
 * @return 
 */  
public static RSAPrivateKey getPrivateKey(String modulus, String exponent) {  
    try {  
        BigInteger b1 = new BigInteger(modulus);  
        BigInteger b2 = new BigInteger(exponent);  
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");  
        RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(b1, b2);  
        return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);  
    } catch (Exception e) {  
        e.printStackTrace();  
        return null;  
    }  
}  

/** 
 * 公钥加密 
 *  
 * @param data 
 * @param publicKey 
 * @return 
 * @throws Exception 
 */  
public static String encryptByPublicKey(String data, RSAPublicKey publicKey)  
        throws Exception {  
    Cipher cipher = Cipher.getInstance("RSA");  
    cipher.init(Cipher.ENCRYPT_MODE, publicKey);  
    // 模长  
    int key_len = publicKey.getModulus().bitLength() / 8;  
    // 加密数据长度 <= 模长-11  
    String[] datas = splitString(data, key_len - 11);  
    String mi = "";  
    //如果明文长度大于模长-11则要分组加密  
    for (String s : datas) {  
        mi += bcd2Str(cipher.doFinal(s.getBytes()));  
    }  
    return mi;  
}  

/** 
 * 私钥解密 
 *  
 * @param data 
 * @param privateKey 
 * @return 
 * @throws Exception 
 */  
public static String decryptByPrivateKey(String data, RSAPrivateKey privateKey)  
        throws Exception {  
    Cipher cipher = Cipher.getInstance("RSA");  
    cipher.init(Cipher.DECRYPT_MODE, privateKey);  
    //模长  
    int key_len = privateKey.getModulus().bitLength() / 8;  
    byte[] bytes = data.getBytes();  
    byte[] bcd = ASCII_To_BCD(bytes, bytes.length);  
    System.err.println(bcd.length);  
    //如果密文长度大于模长则要分组解密  
    String ming = "";  
    byte[][] arrays = splitArray(bcd, key_len);  
    for(byte[] arr : arrays){  
        ming += new String(cipher.doFinal(arr));  
    }  
    return ming;  
}  
/** 
 * ASCII码转BCD码 
 *  
 */  
public static byte[] ASCII_To_BCD(byte[] ascii, int asc_len) {  
    byte[] bcd = new byte[asc_len / 2];  
    int j = 0;  
    for (int i = 0; i < (asc_len + 1) / 2; i++) {  
        bcd[i] = asc_to_bcd(ascii[j++]);  
        bcd[i] = (byte) (((j >= asc_len) ? 0x00 : asc_to_bcd(ascii[j++])) + (bcd[i] << 4));  
    }  
    return bcd;  
}  
public static byte asc_to_bcd(byte asc) {  
    byte bcd;  

    if ((asc >= '0') && (asc <= '9'))  
        bcd = (byte) (asc - '0');  
    else if ((asc >= 'A') && (asc <= 'F'))  
        bcd = (byte) (asc - 'A' + 10);  
    else if ((asc >= 'a') && (asc <= 'f'))  
        bcd = (byte) (asc - 'a' + 10);  
    else  
        bcd = (byte) (asc - 48);  
    return bcd;  
}  
/** 
 * BCD转字符串 
 */  
public static String bcd2Str(byte[] bytes) {  
    char temp[] = new char[bytes.length * 2], val;  

    for (int i = 0; i < bytes.length; i++) {  
        val = (char) (((bytes[i] & 0xf0) >> 4) & 0x0f);  
        temp[i * 2] = (char) (val > 9 ? val + 'A' - 10 : val + '0');  

        val = (char) (bytes[i] & 0x0f);  
        temp[i * 2 + 1] = (char) (val > 9 ? val + 'A' - 10 : val + '0');  
    }  
    return new String(temp);  
}  
/** 
 * 拆分字符串 
 */  
public static String[] splitString(String string, int len) {  
    int x = string.length() / len;  
    int y = string.length() % len;  
    int z = 0;  
    if (y != 0) {  
        z = 1;  
    }  
    String[] strings = new String[x + z];  
    String str = "";  
    for (int i=0; i<x+z; i++) {  
        if (i==x+z-1 && y!=0) {  
            str = string.substring(i*len, i*len+y);  
        }else{  
            str = string.substring(i*len, i*len+len);  
        }  
        strings[i] = str;  
    }  
    return strings;  
}  
/** 
 *拆分数组  
 */  
public static byte[][] splitArray(byte[] data,int len){  
    int x = data.length / len;  
    int y = data.length % len;  
    int z = 0;  
    if(y!=0){  
        z = 1;  
    }  
    byte[][] arrays = new byte[x+z][];  
    byte[] arr;  
    for(int i=0; i<x+z; i++){  
        arr = new byte[len];  
        if(i==x+z-1 && y!=0){  
            System.arraycopy(data, i*len, arr, 0, y);  
        }else{  
            System.arraycopy(data, i*len, arr, 0, len);  
        }  
        arrays[i] = arr;  
    }  
    return arrays;  
}  

}


##### 2.4 问题 
 > 公钥(n,e) 只能 加密小于 n 的整数 m ,那么如果要加密大于 n 的整数,怎么办?
 > 在 Java 中 进行 RSA 加密时,有 一个 错误为 ArrayIndexOutOfBoundsException: too much data for RSA block
 > 该错误就是加密数据过长导致的。

这里涉及到几个知识点 -- **密钥长度/密文长度/明文长度**
1. 明文长度
    明文长度(bytes) **<**= 密钥长度(bytes)-11.
    如果 明文长度 大于 规定,则出现上述的问题,可以按照下文中的解决方法处理
2. 密钥长度
    下限是96bits(12bytes)
    上限未知。不过目前为止,被破解的最长的密钥长度 为 768位,所以 1024 位基本安全, 2048 位绝对安全
3. 密文长度
    - 不分片加密 -- 密文长度 == 密钥长度
    - 分片加密-- 密文长度 == 密钥长度*分片数
        例如 明文 8 bytes , 密钥 128 bits
        每片明文长度 = 128/8 - 11 = 5 bytes
        分片数 = 8/5 +1 = 2
        密文长度 = 128/8 * 2 = 32 bytes
    
**解决方法**
1. 分片加密 -- 是把长信息分割成若干段短消息,每段分别加密;
2. 先选择一种"对称性加密算法"(比如DES),用这种算法的密钥加密信息,再用RSA公钥加密DES密钥。

未完待续。。。

## 结语
发现排版,好像是有问题的,阅读效果不理想,可以去我的[个人博客](https://3dot141.cn)中。

都看到这里了,点个**关注**,点波**赞**再走,QAQ。
你的小手**轻点**,是我最大的动力哦。
> 一只想当程序员的1米88**处女座**大可爱如此说道。


Allen
232 声望25 粉丝

一辈子很短,如白驹过隙转瞬即逝,而这种心情很长,如高山大川,连绵不绝。