killBase系列 -- 密码学

前言

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

密码学

一、基础

  1. 密码学算法分类:
    • 消息编码:Base64
    • 消息摘要:MD类, SHA类,MAC
    • 对称密码:DES,3DES,AES
    • 非对称密码:RSA,DH
    • 数字签名:RSASignature,DSASignature
  2. 五元组
    1)明文:原始信息。
    2)加密算法:以密钥为参数,对明文进行多种置换和转换的规则和步骤,变换结果为密文。
    3)解密算法:加密算法的逆变换,以密文为输入、密钥为参数,变换结果为明文。:
    4)密钥:加密与解密算法的参数,直接影响对明文进行变换的结果。
    5)密文:对明文进行变换的结果。
  3. 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
  4. 常用开源工具
    • Commons.Codec
    • Bouncy.Castle

二、Base64 算法

  1. Base64 基于64个字符编码算法,以任意 8 位字节序列组合描述形式 , BASE加密后产生的字节位数是8的倍数,如果不够位数以=符号填充。对此 Base64 算法有一套字符映射表。
  2. 使用方法:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    复制代码// 获取
    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. 主要方法:
    1
    2
    复制代码// xxx 可以为 md5,sha
    MessageDigest.getInstance("xxx")

1. MD5算法

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

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
复制代码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)转换成报文摘要(不同的明文对应不同的报文摘要),报文摘要加密后与明文一起传送给接受方,接受方将接受的明文产生新的报文摘要与发送方的发来报文摘要解密比较,比较结果一致表示明文未被改动,如果不一致表示明文已被篡改。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
复制代码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,并将其加入到消息中,然后传输。接收方利用与发送方共享的密钥进行鉴别认证 等。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
复制代码    // 构建密钥
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)一起经过特殊加密算法处理后,使其变成复杂的加密密文发送出去。
    收信方收到密文后,若想解读原文,则需要使用加密用过的密钥相同算法逆算法对密文进行解密,才能使其恢复成可读明文。
    在对称加密算法中,使用的密钥只有一个,发收信双方都使用这个密钥对数据进行加密和解密,这就要求解密方事先必须知道加密密钥。
  2. 优缺点
* 优点:算法公开、计算量小、加密速度快、加密效率高。
* 缺点:  
(1)交易双方都使用同样钥匙,安全性得不到保证。  
(2)每对用户每次使用对称加密算法时,都需要使用其他人不知道的惟一钥匙,这会使得发收信双方所拥有的钥匙数量呈几何级数增长,  
**密钥管理**成为用户的负担。对称加密算法在分布式网络系统上使用较为困难,主要是因为密钥管理困难,使用成本较高。
  1. 常用的对称加密算法。
    DES(Data Encryption Standard):数据加密标准,速度较快,适用于加密大量数据的场合。
    3DES(Triple DES):是基于DES,对一块数据用三个不同的密钥进行三次加密,强度更高。
    AES(Advanced Encryption Standard):高级加密标准,是下一代的加密算法标准,速度快,安全级别最高
  2. 对称密码常用的数学运算
    • 移位和循环移位
        移位就是将一段数码按照规定的位数整体性地左移或右移。循环右移就是当右移时,把数码的最后的位移到数码的最前头,循环左移正相反。例如,对十进制数码12345678循环右移1位(十进制位)的结果为81234567,而循环左移1位的结果则为23456781。
    • 置换
        就是将数码中的某一位的值根据置换表的规定,用另一位代替。它不像移位操作那样整齐有序,看上去杂乱无章。这正是加密所需,被经常应用。
    • 扩展
        就是将一段数码扩展成比原来位数更长的数码。扩展方法有多种,例如,可以用置换的方法,以扩展置换表来规定扩展后的数码每一位的替代值。
    • 压缩
        就是将一段数码压缩成比原来位数更短的数码。压缩方法有多种,例如,也可以用置换的方法,以表来规定压缩后的数码每一位的替代值。
    • 异或
        这是一种二进制布尔代数运算。异或的数学符号为⊕ ,它的运算法则如下:
      1⊕ 1 = 0
      0⊕ 0 = 0
      1⊕ 0 = 1
      0⊕ 1 = 1
        也可以简单地理解为,参与异或运算的两数位如相等,则结果为0,不等则为1。
    • 迭代
        迭代就是多次重复相同的运算,这在密码算法中经常使用,以使得形成的密文更加难以破解。
  3. 分组加密
    参考 分组加密的四种模式
    ECB模式 – 电子密码本模式
    CBC模式 – 密码分组链接模式
    CFB模式 – 密文反馈模式
    OFB模式 – 输出反馈模式
    CTR模式 – 计数器模式
  4. 常用的填充方式
    在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 实现

相关的类

1
2
3
4
5
6
复制代码// 生成密钥
KeyGenerator,SecretKeyFactory
// 密钥
SecretKey , SecretKeySpec
// 密码
Cipher

这里重点讲一下 Cipher 类

  1. 首先要设置参数
    Cipher.getInstance(加解密算法,加解密模式,填充模式)
  2. 初始化
    Cipher.init(加解密模式 – Cypher.ENCRIPT/DECRYPT,密钥)
  3. 完成加解密
    Cipher.doFinal(bytes) – 将bytes 内容 加密/解密 然后返回。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
复制代码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
    p = 61, q = 53

  2. 计算 p 和 q 的乘积 n
    n = 61*53 = 3233

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

  4. 随机选择一个整数 e , 条件是 1 *<* e *<* φ(n) , 且 e 与 φ(n) 互质
    e = 17 ( 实际应用中,常常选择 65537 )

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    复制代码 所谓"模反元素"就是指有一个整数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
2
3
4
5
> 复制代码  (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 这样的数字,历史上最大的已经进行因数分解的整数为

1
2
3
4
5
6
7
8
9
复制代码  12301866845301177551304949
  58384962720772853569595334
  79219732245215172640050726
  36575187452021997864693899
  56474942774063845925192557
  32630345373154826850791702
  61221429134616704292143116
  02221240479274737794080665
  351419597459856902143413

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

1
2
3
4
5
6
7
8
9
10
11
复制代码  33478071698956898786044169
  84821269081770479498371376
  85689124313889828837938780
  02287614711652531743087737
  814467999489
    ×
  36746043666799590428244633
  79962795263227915816434308
  76426760322838157396665112
  79233373417143396810270092
  798736308917

破解: 这里有一篇关于 RSA 破解的文章,有兴趣的同学可以看一下。
RSA计时攻击

2.3 Java 实现

使用到的类: java.security

1
2
3
4
5
6
7
8
9
10
复制代码// 生成 公钥,密钥
KeyPairGenerator --> KeyPair , KeyFactory --> RSA XXX Spec
// 公钥 密钥
KeyPair
RSAPublicKeySpec --> RSAPublicKey
RSAPrivateKeySpec --> RSAPrivateKey
// 密码
Cipher -- 1.Cipher.getInstance("RSA")
2.init(mode, key)
3.cipher.doFinal()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
复制代码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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
复制代码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密钥。

附录

1. DES 详细加密过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
复制代码1. **对输入的密钥进行变换**。
用户的64bit密钥,其中第8, 16, 24, 32, 40, 48, 56, 64位是校验位, 使得每个密钥都有奇数个1。所以密钥事实上是56位。对这56位密钥进行如下表的换位。

57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36,
63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4,

表的意思是第57位移到第1位,第49位移到第2位,...... 以此类推。变换后得到56bit数据,将它分成两部分,C[0][28], D[0][28]。

2. **计算16个子密钥**,计算方法C[i][28] D[i][28]为对前一个C[i-1][28], D[i-1][28]做循环左移操作。16次的左移位数如下表:

1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 (第i次)
1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 (左移位数)

3. **串联**计算出来的C[i][28] D[i][28] 得到56位,然后对它进行如下变换得到48位子密钥K[i][48]

14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10, 23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2,
41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48, 44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32,

表的意思是第14位移到第1位,第17位移到第2位,以此类推。在此过程中,发现第9,18,22,25, 35,38,43,54位丢弃。

4. 对64bit的明文输入进行换位变换。换位表如下:

58, 50, 12, 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

表的意思就是第一次变换时,第58位移到第1位,第50位移到第2位,...... 依此类推。得到64位数据,将这数据前后分成两块L[0][32], R[0][32]。

5. 加密过程,对R[i][32]进行扩展变换成48位数,方法如下, 记为E(R[i][32])

32, 1, 2, 3, 4, 5,
4, 5, 6, 7, 8, 9,
8, 9, 10, 11, 12, 13,
12, 13, 14, 15, 16, 17,
16, 17, 18, 19, 20, 21,
20, 21, 22, 23, 24, 25,
24, 25, 26, 27, 28, 29,
28, 29, 30, 31, 32, 1,

6. 将E(R[i][32])与K[i][48]作异或运算,得到48位数,将48位数顺序分成8份,6位一份,B[8][6]。

7. 使用S[i]替换B[i][6]。过程如下: 取出B[i][6]的第1位和第6位连成一个2位数m, m就是S[i]中对应的行数(0-3),取出B[i][6]的第2到第5位连成一个4位数n(0-15),n就是S[i]中对应的列数,用S[i][m][n]代替B[i][6]。S是4行16列的对应表,里面是4位的数,一共有8个S,定义如下:

S[1]:
14,4,13,1,2,15,11,8,3,10,6,12,5,9,0,7,
  0,15,7,4,14,2,13,1,10,6,12,11,9,5,3,8,
  4,1,14,8,13,6,2,11,15,12,9,7,3,10,5,0,
  15,12,8,2,4,9,1,7,5,11,3,14,10,0,6,13,
S[2]:
15,1,8,14,6,11,3,4,9,7,2,13,12,0,5,10,
3,13,4,7,15,2,8,14,12,0,1,10,6,9,11,5,
0,14,7,11,10,4,13,1,5,8,12,6,9,3,2,15,
13,8,10,1,3,15,4,2,11,6,7,12,0,5,14,9,
S[3]:
10,0,9,14,6,3,15,5,1,13,12,7,11,4,2,8,
13,7,0,9,3,4,6,10,2,8,5,14,12,11,15,1,
13,6,4,9,8,15,3,0,11,1,2,12,5,10,14,7,
1,10,13,0,6,9,8,7,4,15,14,3,11,5,2,12,
S[4]:
7,13,14,3,0,6,9,10,1,2,8,5,11,12,4,15,
  13,8,11,5,6,15,0,3,4,7,2,12,1,10,14,9,
  10,6,9,0,12,11,7,13,15,1,3,14,5,2,8,4,
  3,15,0,6,10,1,13,8,9,4,5,11,12,7,2,14,
S[5]:
  2,12,4,1,7,10,11,6,8,5,3,15,13,0,14,9,
  14,11,2,12,4,7,13,1,5,0,15,10,3,9,8,6,
  4,2,1,11,10,13,7,8,15,9,12,5,6,3,0,14,
  11,8,12,7,1,14,2,13,6,15,0,9,10,4,5,3,
S[6]:
  12,1,10,15,9,2,6,8,0,13,3,4,14,7,5,11,
  10,15,4,2,7,12,9,5,6,1,13,14,0,11,3,8,
  9,14,15,5,2,8,12,3,7,0,4,10,1,13,11,6,
  4,3,2,12,9,5,15,10,11,14,1,7,6,0,8,13,
S[7]:
  4,11,2,14,15,0,8,13,3,12,9,7,5,10,6,1,
  13,0,11,7,4,9,1,10,14,3,5,12,2,15,8,6,
  1,4,11,13,12,3,7,14,10,15,6,8,0,5,9,2,
  6,11,13,8,1,4,10,7,9,5,0,15,14,2,3,12,
S[8]:
  13,2,8,4,6,15,11,1,10,9,3,14,5,0,12,7,
  1,15,13,8,10,3,7,4,12,5,6,11,0,14,9,2,
  7,11,4,1,9,12,14,2,0,6,10,13,15,3,5,8,
  2,1,14,7,4,10,8,13,15,12,9,0,3,5,6,11,

8. 将从B[i][6]经过S得到的8个4位数连起来得到32位数。对这个数进行如下变换:

16,7,20,21,29,12,28,17, 1,15,23,26, 5,18,31,10,
  2,8,24,14,32,27, 3, 9,19,13,30, 6,22,11, 4,25,

得到的结果与L[i][32]作异或运算,把结果赋给R[i][32]。

9. 把R[i-1][32]的值赋给L[i],从5开始循环。直到K[16][48]结束。

10. 将最后的L,R合并成64位,然后进行如下转化得到最后的结果。这是对第4步的一个逆变化。
40, 8, 48, 16, 56, 24, 64, 32,
39, 7, 47, 15, 55, 23, 63, 31,
38, 6, 46, 14, 54, 22, 62, 30,
37, 5, 45, 13, 53, 21, 61, 29,
36, 4, 44, 12, 52, 20, 60, 28,
35, 3, 43, 11, 51, 19, 59, 27,
34, 2, 42, 10, 50, 18, 58, 26,
33, 1, 41, 9, 49, 17, 57, 25

2. https 的加密算法

由于之前看过 https 是 由 secure socket layer 实现的。 也是通过 公钥私钥 保证其安全性,所以在学习这篇文章的时候,就想 https 是由哪种 加密算法 做为其 底层实现的呢。 因此,就有了下面这部分。

关于 https 与 http 的区别 请看我的这篇博客,不再赘述。网络基础知识

原理:

  • 浏览器把自身支持的一系列Cipher Suite(密钥算法套件,后文简称Cipher)[C1,C2,C3, …]发给服务器;
  • 服务器接收到浏览器的所有Cipher后,与自己支持的套件作对比,如果找到双方都支持的Cipher,则告知浏览器;
  • 浏览器与服务器使用匹配的Cipher进行后续通信。如果服务器没有找到匹配的算法,浏览器(以 Chrome 56为例)将给出错误信息:

下面讲一下如何分析。

  1. 准备: 通过可以抓取网络包的工具,这里通过 Wireshark 分析。关于wireshark 的介绍请点击这里.查看浏览器发送给服务器的 Ciper服务器的 Ciper
  2. 流程:
    • 浏览器首先发起握手协议, 一个’Client Hello’消息,如下图,按照Protocol协议顺序排序,然后,找到Client Hello,选中,依次查找 ‘Secure Sockets Layer’ -> TLSv1.2 Record Layer -> Handshake protocal ->Ciper Suites.
    • 可以看到, Cipher有很多。总共16,第一个是Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b)。
    • 如果按照顺序继续寻找第一个 Info 为’Sever Hello’ 的报文,可以找到相应的Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b) 。
.
  1. Cipher介绍:
    • 密钥交换算法,用于决定客户端与服务器之间在握手的过程中如何认证,用到的算法包括RSA,Diffie-Hellman,ECDH,PSK等
    • 加密算法,用于加密消息流,该名称后通常会带有两个数字,分别表示密钥的长度和初始向量的长度,比如DES 56/56, RC2 56/128, RC4 128/128, AES 128/128, AES 256/256
    • 报文认证信息码(MAC)算法,用于创建报文摘要,确保消息的完整性(没有被篡改),算法包括MD5,SHA等。
    • PRF(伪随机数函数),用于生成“master secret”。
    • TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b):
      • 基于TLS协议
      • 使用 ECDHE,ECDSA作为密钥交换算法
      • 加密算法 AES(密钥与初始向量的长度为128)
      • MAC 算法 SHA
  2. 总结:
    Client端密钥算法套件[C1,C2,C3],Server端密钥算法套件[C4,C2,C1,C3],
    则,IIS(Internet Infomation Services),C2将被优先返回

3. wireshark 的使用问题

问题:第一次使用 wireshark 的时候,不显示接口。原因是。。。
刚开始使用 在windows 上需要 winpacp 并且开启 npf 服务。
注: 如果 没有安装 winpacp ,想直接 通过 net start npf 开启服务,将会提示。 发生系统错误2

  1. winpacp 安装 。。。
    这里是下载网站
    直接安装即可。
  2. 开启 npf 服务
    打开 cmd ,输入 net start npf ,提示:服务已经启动。
  3. 进入界面,选择相应的网卡。

这里,可以通过 网络连接 看出来。

所以,我的是无线网络连接。
4. 最终界面

WireShark 主要分为这几个界面
5. Display Filter(显示过滤器), 用于过滤
6. Packet List Pane(封包列表), 显示捕获到的封包, 有源地址和目标地址,端口号。 颜色不同,代表
7. Packet Details Pane(封包详细信息), 显示封包中的字段
8. Dissector Pane(16进制数据)
9. Miscellanous(地址栏,杂项)

结语

都看到这里了,点个关注,点波再走,QAQ。
你的小手轻点,是我最大的动力哦。

一只想当程序员的1米88处女座大可爱如此说

参考

  1. DES 加密算法解析
  2. 分组加密的四种模式
  3. 阮一峰–RSA算法原理
  4. java中RSA加解密的实现
  5. 关于RSA算法密钥长度/密文长度/明文长度
  6. https背后的加密算法

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%