Java 常用加密算法使用与整理

96
羽杰
0.3 2018.01.03 16:50* 字数 5045

文前说明

作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。

本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。

1. AES

  • 高级加密标准(英语:Advanced Encryption Standard,缩写:AES)。
  • 密码学 中又称 Rijndael加密法,是美国联邦政府采用的一种区块加密标准。
  • AES 的区块长度固定为 128 比特,每个加密块大小为 128 位。,密钥长度则可以是 128192256 比特。
  • JCE 中 AES 支持五中模式(CBCCFBECBOFBPCBC)。
  • 支持三种填充(NoPaddingPKCS5PaddingISO10126Padding)。
  • 不支持 SSL3Padding。
  • 不支持 NONE 模式。
  • 不带模式和填充获取 AES 算法,默认使用 ECB/PKCS5Padding
算法/模式/填充 16 字节加密后数据长度 不满 16 字节加密后数据长度 必须设置偏移量
AES/CBC/NoPadding 16 不支持
AES/CBC/PKCS5Padding 32 16
AES/CBC/ISO10126Padding 32 16
AES/CFB/NoPadding 16 原始数据长度
AES/CFB/PKCS5Padding 32 16
AES/CFB/ISO10126Padding 32 16
AES/ECB/NoPadding 16 不支持 ×
AES/ECB/PKCS5Padding 32 16 ×
AES/ECB/ISO10126Padding 32 16 ×
AES/OFB/NoPadding 16 原始数据长度
AES/OFB/PKCS5Padding 32 16
AES/OFB/ISO10126Padding 32 16
AES/PCBC/NoPadding 16 不支持
AES/PCBC/PKCS5Padding 32 16
AES/PCBC/ISO10126Padding 32 16
  • 在原始数据长度为 16 的整数倍时,假如原始数据长度等于 16*n,则使用 NoPadding
    时加密后数据长度等于 16*n
  • 其它情况下加密数据长度等于 16*(n+1)
  • 不足 16 的整数倍的情况下,假如原始数据长度等于 16*n+m [其中 m 小于 16 ],除了NoPadding 填充之外的任何方式,加密数据长度都等于 16*(n+1)
  • NoPadding 填充情况下,CBCECBPCBC 三种模式是不支持的,CFBOFB 两种模式下则加密数据长度等于原始数据长度。

不支持的情况,给出错误提示

javax.crypto.IllegalBlockSizeException: Input length not multiple of 16 bytes

必须设置 iv 参数但缺失的情况,给出错误提示

java.security.InvalidKeyException: Parameters missing

1.1 JCE 组包

  • Java Cryptography Extension(JCE)是一组包,它们提供用于加密、密钥生成和协商以及 Message Authentication Code(MAC)算法的框架和实现。
  • 它提供对对称、不对称、块和流密码的加密支持,它还支持安全流和密封的对象。
  • 它不对外出口,用它开发完成封装后将无法调用。

1.2 加密模式

1.2.1 ECB 模式(电子密码本模式:Electronic codebook)

  • ECB 是最简单的块密码加密模式,加密前根据加密块大小(如 AES 为
    128 位)分成若干块,之后将每块使用相同的密钥单独加密,解密同理。
ECB 加密流程(图片来自维基百科)
ECB 解密流程(图片来自维基百科)
  • ECB 模式由于每块数据的加密是独立的因此加密和解密都可以并行计算。
  • ECB 模式最大的缺点是相同的明文块会被加密成相同的密文块,这种方法在某些环境下不能提供严格的数据保密性。

1.2.2 CBC 模式(密码分组链接:Cipher-block chaining)

  • CBC 模式对于每个待加密的密码块在加密前会先与前一个密码块的密文异或然后再用加密器加密。
  • 第一个明文块与一个叫初始化向量(偏移量 iv)的数据块异或。
CBC 加密流程(图片来自维基百科)
CBC 解密流程(图片来自维基百科)
  • CBC 模式相比 ECB 有更高的保密性,但由于对每个数据块的加密依赖与前一个数据块的加密所以加密无法并行。
  • 与 ECB 一样在加密前需要对数据进行 填充,不是很适合对流数据进行加密。

1.2.3 CFB 模式(密文反馈:Cipher feedback)

  • 与 ECB 和 CBC 模式只能够加密块数据不同,CFB 能够将块密文(Block Cipher)转换为流密文(Stream Cipher)。
CFB 加密流程(图片来自维基百科)
CFB 解密流程(图片来自维基百科)

注意:CFBOFBCTR 模式中解密也都是用的加密器而非解密器。

  • CFB 模式非常适合对流数据进行加密,解密可以并行计算。
1.2.3.1 CFB128
  • CFB的加密工作分为两部分
    • 将一前段加密得到的密文再加密。
    • 将第一步加密得到的数据与当前段的明文异或。
  • 由于加密流程和解密流程中被块加密器加密的数据是前一段密文,因此即使明文数据的长度不是加密块大小的整数倍也是 不需要填充 的,这保证了数据长度在加密前后是相同的。
  • 128 位的 CFB 是对前一段数据的密文用块加密器加密后保存在 iv 中,之后用这 128 位数据与后面到来的 128 位数据异或。
  • CFB128 是每处理 128 位数据调用一次加密器。
1.2.3.2 CFB8
  • 每处理 8 位调用一次加密器。
  • CFB8 的加密流程
    • 使用加密器加密 iv 的数据。
    • 将明文的最高 8 位与 iv 的最高 8 位异或得到 8 位密文。
    • 将 iv 数据左移 8 位,最低 8 位用刚刚计算得到的 8 位密文补上。
1.2.3.3 CFB1
  • 每处理 1 位调用一次加密器。
  • CFB1 的加密流程
    • 使用加密器加密 iv 的数据。
    • 将明文的最高 1 位与 iv 的最高 1 位异或得到 1 位密文。
    • 将 iv 数据左移 1 位,最低 1 位用刚刚计算得到的 1 位密文补上。

1.2.4 OFB 模式(输出反馈:Output feedback)

  • OFB 是先用块加密器生成密钥流(Keystream),然后再将密钥流与明文流异或得到密文流。
  • 解密是先用块加密器生成密钥流,再将密钥流与密文流异或得到明文,由于异或操作的对称性所以 加密和解密的流程是完全一样的
OFB 加密流程(图片来自维基百科)
OFB 解密流程(图片来自维基百科)
  • OFB 与 CFB 一样都非常适合对流数据的加密。
  • OFB 由于加密和解密都依赖与前一段数据,所以加密和解密都不能并行。

1.3 AES 在 Java 中的使用

1.3.1 AES 密钥生成者

  • 创建 AES Key 的生产者。
KeyGenerator kgen = KeyGenerator.getInstance("AES");
  • 利用用户密码作为随机数初始化出 128 比特 Key 的生产者。
  • SecureRandom 是生成安全随机数序列,password.getBytes() 是种子,只要种子相同,序列就一样。
kgen.init(128, new SecureRandom(password.getBytes()));
  • 根据用户密码,生成一个密钥。
SecretKey secretKey = kgen.generateKey();
  • 返回基本编码格式的密钥,如果此密钥不支持编码,则返回 null。
byte[] raw = secretKey.getEncoded();

1.3.2 AES 密钥自定义

byte[] raw = password.getBytes("utf-8");

密钥长度只能为 1624 或者 32 位,否则给出错误提示。

java.security.InvalidKeyException: Invalid AES key length: 4 bytes
java.security.InvalidKeyException: Invalid AES key length: 48 bytes

1.3.3 生成 AES 专用密钥

SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");

1.3.4 AES 加密

  • 创建密码器
Cipher cipher = Cipher.getInstance("AES");
  • 设置模式为加密模式
 cipher.init(Cipher.ENCRYPT_MODE, key);
  • 对内容进行加密
byte[] result = cipher.doFinal(content.getBytes("utf-8"));

1.3.5 AES 解密

  • 密钥的定义方式和密码器的创建与加密相同。

  • 设置模式为解密模式

cipher.init(Cipher.DECRYPT_MODE, key);
  • 对内容进行解密
byte[] result = cipher.doFinal(content);

1.3.6 偏移量 iv 定义

  • ECB 模式以外的其它模式都需要设置偏移量,用以增加加密算法的强度。
IvParameterSpec iv = new IvParameterSpec(ivParameter.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

偏移量 iv 长度必须为密钥位数一致,否则给出错误提示

java.security.InvalidAlgorithmParameterException: Wrong IV length: must be 16 bytes long

ECB 模式设置偏移量,给出错误提示

java.security.InvalidAlgorithmParameterException: ECB mode cannot use IV

1.3.7 进制转换

  • 为了解决加解密出现乱码问题需要进行进制转换。
  • 将二进制转换成十六进制
public static String parseByte2HexStr(byte buf[]) {  
        StringBuffer sb = new StringBuffer();  
        for (int i = 0; i < buf.length; i++) {  
                String hex = Integer.toHexString(buf[i] & 0xFF);  
                if (hex.length() == 1) {  
                        hex = '0' + hex;  
                }  
                sb.append(hex.toUpperCase());  
        }  
        return sb.toString();  
} 
  • 将十六进制转换为二进制
public static byte[] parseHexStr2Byte(String hexStr) {  
        if (hexStr.length() < 1)  
                return null;  
        byte[] result = new byte[hexStr.length()/2];  
        for (int i = 0;i< hexStr.length()/2; i++) {  
                int high = Integer.parseInt(hexStr.substring(i*2, i*2+1), 16);  
                int low = Integer.parseInt(hexStr.substring(i*2+1, i*2+2), 16);  
                result[i] = (byte) (high * 16 + low);  
        }  
        return result;  
}
  • 加密内容不显示乱码,可以先将密文转换为十六进制。
String hexStrResult = parseByte2HexStr(result);
  • 十六进制密文,先转为二进制再进行解密。
byte[] twoStrResult = parseHexStr2Byte(hexStrResult);

2. DES

  • DES 全称为 Data Encryption Standard,即数据加密标准,是一种使用 密钥加密 的块算法
  • 1977 年被 美国联邦政府 的国家标准局确定为 联邦资料处理标准(FIPS),并授权在非密级政府通信中使用,随后该算法在国际上广泛流传开来。
  • AES 的区块长度固定为 64 比特,每个加密块大小为 64 位。,密钥长度是 64 位(实际用到了 56 位,第 8、16、24、32、40、48、56、64 位是校验位, 使得每个密钥都有奇数个 1)。
DES 算法流程
  • DES 算法主要分为两步
    • 初始置换
    • 逆置换
  • 初始置换
    • 把输入的 64 位数据块按位重新组合,并把输出分为 L0、R0 两部分。每部分各长32位。
    • 其置换规则为将输入的第 58 位换到第一位,第 50 位换到第 2 位……依此类推,最后一位是原来的第 7 位。
    • L0、R0 则是换位输出后的两部分,L0 是输出的左 32 位,R0 是右
      32 位。
    • 例:设置换前的输入值为 D1D2D3……D64,则经过初始置换后的结果为:L0=D58D50……D8;R0=D57D49……D7。
  • 其置换规则如下表:
1 2 3 4 5 6 7 8
58 50 42 34 26 18 10 2
60 52 44 36 28 20 12 4
62 54 46 38 30 22 14 6
64 56 48 40 32 24 16 8
57 49 41 33 25 17 9 1
59 51 43 35 27 19 11 3
61 53 45 37 29 21 13 5
63 55 47 39 31 23 15 7
  • 逆置换
    • 经过 16 次迭代运算后,得到 L16、R16,将此作为输入,进行逆置换,逆置换正好是初始置换的逆运算,由此即得到密文输出。

2.1 DES 在 Java 中的使用

2.1.1 生成 DES 专用密钥

SecretKeySpec key = new SecretKeySpec(enCodeFormat, "DES");

2.1.2 DES 加密

  • 创建密码器
Cipher cipher = Cipher.getInstance("DES");
  • 设置模式为加密模式
 cipher.init(Cipher.ENCRYPT_MODE, key);
  • 对内容进行加密
byte[] result = cipher.doFinal(content.getBytes("utf-8"));

2.1.3 DES 解密

  • 密钥的定义方式和密码器的创建与加密相同。

  • 设置模式为解密模式

cipher.init(Cipher.DECRYPT_MODE, key);
  • 对内容进行解密
byte[] result = cipher.doFinal(content);

3. MD5

  • Message Digest Algorithm MD5(中文名为 消息摘要算法 第五版)为计算机安全领域广泛使用的一种散列函数,用以提供消息的完整性保护。
  • 该算法的文件号为 RFC 1321(R.Rivest,MIT Laboratory for Computer Science and RSA Data Security Inc. April 1992)。

3.1 MD5 算法的特点

  • 压缩性:任意长度的数据,算出的 MD5 值长度都是固定的。
  • 容易计算:从原数据计算出 MD5 值很容易。
  • 抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的 MD5 值都有很大区别。
  • 强抗碰撞:已知原数据和其 MD5 值,想找到一个具有相同 MD5 值的数据(即伪造数据)是非常困难的。

3.2 MD5 算法的实现原理

MD5 加密流程

3.2.1 填充

  • 在 MD5 算法中,首先需要对输入信息进行填充,使其位长对 512 求余的结果等于 448,并且填充必须进行,即使其位长对 512 求余的结果等于448。
  • 因此,信息的位长(Bits Length)将被扩展至 N*512+448,N 为一个非负整数,N 可以是零。
  • 填充的方法
    • 在信息的后面 填充一个 1 和无数个 0,直到满足上面的条件时才停止用 0 对信息的填充。
    • 在这个结果后面 附加一个以 64 位二进制表示的 填充前信息长度(单位为Bit),如果二进制表示的填充前信息长度超过 64 位,则取低 64 位。
  • 经过这两步的处理,信息的位长=N*512+448+64=(N+1)*512,即长度恰好是 512 的整数倍。这样做的原因是为满足后面处理中对信息长度的要求。

3.2.2 初始化变量(变量值一般不变)

  • 初始的 128 位值为初试 链接变量,这些参数用于第一轮的运算,以
    大端字节序 来表示。分别为
    • A = 0x01234567
    • B = 0x89ABCDEF
    • C = 0xFEDCBA98
    • D = 0x76543210
  • 每一个变量给出的数值是高字节存于内存低地址,低字节存于内存高地址,即大端字节序。
  • 在程序中变量 A、B、C、D 的值分别为 0x67452301,0xEFCDAB89,0x98BADCFE,0x10325476。

3.2.3 处理分组数据

  • 每一分组的算法流程
    • 第一分组需要将上面四个链接变量复制到另外四个变量中:A 到 a,B
      到 b,C 到 c,D 到 d。
    • 从第二分组开始的变量为上一分组的运算结果,即 A = a, B = b, C = c, D = d。
  • 主循环有四轮(MD4 只有三轮),每轮循环都很相似。第一轮进行 16
    次操作。每次操作对 a、b、c 和 d 中的其中三个作一次非线性函数运算,然后将所得结果加上第四个变量,文本的一个子分组和一个常数。再将所得结果向左环移一个不定的数,并加上 a、b、c 或 d 中之一。最后用该结果取代 a、b、c 或 d 中之一。
  • 一个 MD5 运算由类似的 64 次循环构成,分成 4 组 16 次。
处理分组数据流程
  • F:一个非线性函数,一个函数运算一次。
  • Mi:表示一个 32-bits 的输入数据。
  • Ki:表示一个 32-bits 常数,用来完成每次不同的计算。
  • 每次操作中用到的四个非线性函数(每轮一个)。
    • F( X ,Y ,Z ) = ( X & Y ) | ( (~X) & Z )
    • G( X ,Y ,Z ) = ( X & Z ) | ( Y & (~Z) )
    • H( X ,Y ,Z ) = X ^ Y ^ Z
    • I( X ,Y ,Z ) = Y ^ ( X | (~Z) )
  • & 是与(And),| 是或(Or),~ 是非(Not),^ 是异或(Xor)
  • 如果 X、Y 和 Z 的对应位是独立和均匀的,那么结果的每一位也应是独立和均匀的。
  • F是一个逐位运算的函数。即,如果 X,那么 Y,否则 Z。
  • H 是逐位奇偶操作符。

用例说明

  • 假设 Mj 表示消息的第 j 个子分组(从0到15)
  • 常数 ti 是4294967296*abs( sin(i) )的整数部分,i 取值从 1 到 64,单位是弧度。(4294967296=2^(32))
  • 现定义
    • FF(a ,b ,c ,d ,Mj ,s ,ti ) 操作为 a = b + ( (a + F(b,c,d) + Mj + ti) << s)
    • GG(a ,b ,c ,d ,Mj ,s ,ti ) 操作为 a = b + ( (a + G(b,c,d) + Mj + ti) << s)
    • HH(a ,b ,c ,d ,Mj ,s ,ti) 操作为 a = b + ( (a + H(b,c,d) + Mj + ti) << s)
    • II(a ,b ,c ,d ,Mj ,s ,ti) 操作为 a = b + ( (a + I(b,c,d) + Mj + ti) << s)

注意:“ << ” 表示循环左移位,不是左移位。

  • 这四轮(共64步)是

  • 第一轮
    FF(a ,b ,c ,d ,M0 ,7 ,0xd76aa478 )
    FF(d ,a ,b ,c ,M1 ,12 ,0xe8c7b756 )
    FF(c ,d ,a ,b ,M2 ,17 ,0x242070db )
    FF(b ,c ,d ,a ,M3 ,22 ,0xc1bdceee )
    FF(a ,b ,c ,d ,M4 ,7 ,0xf57c0faf )
    FF(d ,a ,b ,c ,M5 ,12 ,0x4787c62a )
    FF(c ,d ,a ,b ,M6 ,17 ,0xa8304613 )
    FF(b ,c ,d ,a ,M7 ,22 ,0xfd469501)
    FF(a ,b ,c ,d ,M8 ,7 ,0x698098d8 )
    FF(d ,a ,b ,c ,M9 ,12 ,0x8b44f7af )
    FF(c ,d ,a ,b ,M10 ,17 ,0xffff5bb1 )
    FF(b ,c ,d ,a ,M11 ,22 ,0x895cd7be )
    FF(a ,b ,c ,d ,M12 ,7 ,0x6b901122 )
    FF(d ,a ,b ,c ,M13 ,12 ,0xfd987193 )
    FF(c ,d ,a ,b ,M14 ,17 ,0xa679438e )
    FF(b ,c ,d ,a ,M15 ,22 ,0x49b40821 )

  • 第二轮
    GG(a ,b ,c ,d ,M1 ,5 ,0xf61e2562 )
    GG(d ,a ,b ,c ,M6 ,9 ,0xc040b340 )
    GG(c ,d ,a ,b ,M11 ,14 ,0x265e5a51 )
    GG(b ,c ,d ,a ,M0 ,20 ,0xe9b6c7aa )
    GG(a ,b ,c ,d ,M5 ,5 ,0xd62f105d )
    GG(d ,a ,b ,c ,M10 ,9 ,0x02441453 )
    GG(c ,d ,a ,b ,M15 ,14 ,0xd8a1e681 )
    GG(b ,c ,d ,a ,M4 ,20 ,0xe7d3fbc8 )
    GG(a ,b ,c ,d ,M9 ,5 ,0x21e1cde6 )
    GG(d ,a ,b ,c ,M14 ,9 ,0xc33707d6 )
    GG(c ,d ,a ,b ,M3 ,14 ,0xf4d50d87 )
    GG(b ,c ,d ,a ,M8 ,20 ,0x455a14ed )
    GG(a ,b ,c ,d ,M13 ,5 ,0xa9e3e905 )
    GG(d ,a ,b ,c ,M2 ,9 ,0xfcefa3f8 )
    GG(c ,d ,a ,b ,M7 ,14 ,0x676f02d9 )
    GG(b ,c ,d ,a ,M12 ,20 ,0x8d2a4c8a )

  • 第三轮
    HH(a ,b ,c ,d ,M5 ,4 ,0xfffa3942 )
    HH(d ,a ,b ,c ,M8 ,11 ,0x8771f681 )
    HH(c ,d ,a ,b ,M11 ,16 ,0x6d9d6122 )
    HH(b ,c ,d ,a ,M14 ,23 ,0xfde5380c )
    HH(a ,b ,c ,d ,M1 ,4 ,0xa4beea44 )
    HH(d ,a ,b ,c ,M4 ,11 ,0x4bdecfa9 )
    HH(c ,d ,a ,b ,M7 ,16 ,0xf6bb4b60 )
    HH(b ,c ,d ,a ,M10 ,23 ,0xbebfbc70 )
    HH(a ,b ,c ,d ,M13 ,4 ,0x289b7ec6 )
    HH(d ,a ,b ,c ,M0 ,11 ,0xeaa127fa )
    HH(c ,d ,a ,b ,M3 ,16 ,0xd4ef3085 )
    HH(b ,c ,d ,a ,M6 ,23 ,0x04881d05 )
    HH(a ,b ,c ,d ,M9 ,4 ,0xd9d4d039 )
    HH(d ,a ,b ,c ,M12 ,11 ,0xe6db99e5 )
    HH(c ,d ,a ,b ,M15 ,16 ,0x1fa27cf8 )
    HH(b ,c ,d ,a ,M2 ,23 ,0xc4ac5665 )

  • 第四轮
    II(a ,b ,c ,d ,M0 ,6 ,0xf4292244 )
    II(d ,a ,b ,c ,M7 ,10 ,0x432aff97 )
    II(c ,d ,a ,b ,M14 ,15 ,0xab9423a7 )
    II(b ,c ,d ,a ,M5 ,21 ,0xfc93a039 )
    II(a ,b ,c ,d ,M12 ,6 ,0x655b59c3 )
    II(d ,a ,b ,c ,M3 ,10 ,0x8f0ccc92 )
    II(c ,d ,a ,b ,M10 ,15 ,0xffeff47d )
    II(b ,c ,d ,a ,M1 ,21 ,0x85845dd1 )
    II(a ,b ,c ,d ,M8 ,6 ,0x6fa87e4f )
    II(d ,a ,b ,c ,M15 ,10 ,0xfe2ce6e0 )
    II(c ,d ,a ,b ,M6 ,15 ,0xa3014314 )
    II(b ,c ,d ,a ,M13 ,21 ,0x4e0811a1 )
    II(a ,b ,c ,d ,M4 ,6 ,0xf7537e82 )
    II(d ,a ,b ,c ,M11 ,10 ,0xbd3af235 )
    II(c ,d ,a ,b ,M2 ,15 ,0x2ad7d2bb )
    II(b ,c ,d ,a ,M9 ,21 ,0xeb86d391 )

  • 所有完成之后,将 a、b、c、d 分别在原来基础上再加上 A、B、C、D。

  • 即 a = a + A,b = b + B,c = c + C,d = d + D 然后用下一分组数据继续运行以上算法。

3.2.4 输出

  • 最后的输出是 a、b、c 和 d 的级联。

3.3 MD5 在 Java 中的使用

  • 确定 MD5 计算方法
 MessageDigest md5=MessageDigest.getInstance("MD5");
  • 对内容进行加密
md5.digest(content.getBytes("utf-8"));
  • 同样可以进制转换进行输出显示

4. Base64

  • Base64 是网络上最常见的用于传输8Bit 字节码 的编码方式之一。
  • Base64 是一种基于 64 个可打印字符来表示二进制数据的方法。
  • 可查看 RFC2045~RFC2049,上面有 MIME 的详细规范。
  • Base64 编码是从二进制到字符的过程,可用于在 HTTP 环境下传递较长的标识信息。
  • Base64 要求把每 三个 8 Bit 的字节转换为 四个 6 Bit 的字节(3*8 = 4*6 = 24),然后把 6 Bit 再添两位高位 0,组成四个 8 Bit 的字节,也就是说,转换后的字符串理论上将要比原来的长 1/3。

4.1 Base64 算法规则

  • 把 3 个字符变成 4 个字符。
  • 每 76 个字符加一个换行符。
  • 最后的结束符也要处理。

4.2 Base64 转换表

索引 对应字符 索引 对应字符 索引 对应字符 索引 对应字符
0 A 17 R 34 i 51 z
1 B 18 S 35 j 52 0
2 C 19 T 36 k 53 1
3 D 20 U 37 l 54 2
4 E 21 V 38 m 55 3
5 F 22 W 39 n 56 4
6 G 23 X 40 o 57 5
7 H 24 Y 41 p 58 6
8 I 25 Z 42 q 59 7
9 J 26 a 43 r 60 8
10 K 27 b 44 s 61 9
11 L 28 c 45 t 62 +
12 M 29 d 46 u 63 /
13 N 30 e 47 v
14 O 31 f 48 w
15 P 32 g 49 x
16 Q 33 h 50 y

用例说明

转换前 10101101,10111010,01110110
转换后 00101011, 00011011,00101001,00110110
十进制 43 27 41 54
对应码表中的值 r b p 2
因此上面的 24 位编码,编码后的 Base64 值为 rbp2。解码同理,把 rbq2 的二进制位连接上再重组得到三个 8 位值,得出原码。

  • 一个原字节至少会变成两个目标字节。
  • 余数任何情况下都只可能是 0,1,2 这三个数中的一个。
  • 余数是 0 的话,就表示原文字节数正好是 3 的倍数(最理想的情况)。
  • 如果是 1 的话,转成 2 个 Base64 编码字符,为了让 Base64 编码是 4的倍数,就要补 2 个等号。
  • 如果是 2,就要补 1 个等号。

4.3 Base64 在 Java 中的使用

  • 可以引入 ws-commons-utils 包。
import org.apache.ws.commons.util.Base64;
  • Base64 加密
Base64.encode(result.getBytes());
  • Base64 解密
result = Base64.decode(result.getBytes());

5. RSA

  • RSA 公开密钥密码体制。
  • 所谓的公开密钥密码体制就是使用不同的加密密钥与解密密钥,是一种 “ 由已知加密密钥推导出解密密钥在计算上是不可行的 ” 密码体制。
  • 公开密钥密码体制 中,加密密钥(即公开密钥)PK是公开信息,而解密密钥(即秘密密钥)SK是需要保密的。
  • 加密算法 E 和解密算法 D 也都是公开的。虽然解密密钥 SK 是由公开密钥 PK 决定的,但却不能根据 PK 计算出 SK。

5.1 RSA 在 Java 中的使用

  • 创建 RSA Key 的生产者。
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
  • 利用用户密码作为随机数初始化出 1024 比特 Key 的生产者。
  • SecureRandom 是生成安全随机数序列,password.getBytes() 是种子,只要种子相同,序列就一样。
kgen.init(1024, new SecureRandom(password.getBytes()));
  • 创建密钥对
KeyPair keyPair = keyPairGen.generateKeyPair();
  • 生成公钥
PublicKey publicKey = keyPair.getPublic();
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey.getEncoded());
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(keySpec);
  • 生成私钥
PrivateKey privateKey = keyPair.getPrivate();
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(privateKey.getEncoded());
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
  • 公钥加密
public static byte[] encryptByPublicKey(byte[] data, String publicKey)
            throws Exception {
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(publicKey.getBytes());
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        Key publicK = keyFactory.generatePublic(x509KeySpec);
        // 对数据加密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, publicK);
        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段加密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > 117) {
                cache = cipher.doFinal(data, offSet, 117);
            } else {
                cache = cipher.doFinal(data, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * 117;
        }
        byte[] encryptedData = out.toByteArray();
        out.close();
        return encryptedData;
    }
  • 私钥解密
public static byte[] decryptByPrivateKey(byte[] encryptedData,
            String privateKey) throws Exception {
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(privateKey.getBytes());
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, privateK);
        int inputLen = encryptedData.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段解密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > 118) {
                cache = cipher
                        .doFinal(encryptedData, offSet, 118);
            } else {
                cache = cipher
                        .doFinal(encryptedData, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * 118;
        }
        byte[] decryptedData = out.toByteArray();
        out.close();
        return decryptedData;
    }
  • 私钥加密
public static byte[] encryptByPrivateKey(byte[] data, String privateKey)
            throws Exception {
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(publicKey.getBytes());
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, privateK);
        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段加密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > 117) {
                cache = cipher.doFinal(data, offSet, 117);
            } else {
                cache = cipher.doFinal(data, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * 117;
        }
        byte[] encryptedData = out.toByteArray();
        out.close();
        return encryptedData;
    }
  • 公钥解密
public static byte[] decryptByPublicKey(byte[] encryptedData,
            String publicKey) throws Exception {
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(publicKey.getBytes());
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        Key publicK = keyFactory.generatePublic(x509KeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, publicK);
        int inputLen = encryptedData.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段解密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > 118) {
                cache = cipher
                        .doFinal(encryptedData, offSet, 118);
            } else {
                cache = cipher
                        .doFinal(encryptedData, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * 118;
        }
        byte[] decryptedData = out.toByteArray();
        out.close();
        return decryptedData;
    }
java