kim.zhang

风在前,无惧!


  • 首页

  • 标签42

  • 分类12

  • 归档94

  • 搜索

发表于 2022-01-15 更新于 2022-01-21
本文字数: 15k 阅读时长 ≈ 14 分钟

1.编码

URL编码

URL编码的目的是把任意文本数据编码为%前缀表示的文本,便于浏览器和服务器处理.
如果字符是AZ,az,0~9以及-、_、.、*,则保持不变
如果是其他字符,先转换为UTF-8编码,然后对每个字节以%XX表示

1
2
// URLEncoder是java.net包的
String encoded = URLEncoder.encode("中文!", StandardCharsets.UTF_8);

Base64编码

Base64编码的目的是把任意二进制数据编码为文本,但编码后数据量会增加1/3.
例如,电子邮件协议就是文本协议,如果要在电子邮件中添加一个二进制文件,就可以用Base64编码,然后以文本的形式传送.

Base64编码将二进制数据每3个字节编码成4个字符,如果二进制数据字节数不是3的倍数,会在末尾添加0x00进行补充.

例如:
二进制:e4 b8 ad
二进制的3个字节的数据一共有3 * 8 = 24bit,在使用Base64编码的时候,每6bit编码成一个字符,即24 / 6 = 4个字符.
e4 b8 ad 编码成Base64的结果就是 5Lit

二进制:e4 b8 ad 21
二进制的4个字节的数据一共有4 * 8 = 32bit,在使用Base64编码的时候,每6bit编码成一个字符,即32 / 6 不是一个整数,那就需要在末尾补充0x00,由于Base64编码是每3个字节为单位进行编码的,意味着二进制的数据e4 b8 ad 21需要添加两个0x00凑成6个字节(3的倍数)进行编码,
e4 b8 ad 21 00 00 编码成Base64的结果就是 5LitIQ==, 每一个=代表一个0x00

实际上,在二进制数据编码成Base64过程中在末尾补充的0x00,在Base64解码时是先去掉=的,也就是说即使不添加0x00,也是能正常解码的.
可以使用withoutPadding()在编码时不添加0x00

1
2
byte[] input = new byte[]{(byte)0xe4,(byte)0xb8,(byte)0xad,(byte)0x21};
String encode = Base64.getEncoder().withoutPadding().encodeToString(input);

另外还有一点,Base64编码后的字符加上=的长度一定会是4的倍数.

因为标准的Base64编码会出现+、/和=,所以不适合把Base64编码后的字符串放到URL中.一种针对URL的Base64编码可以在URL中使用的Base64编码,它仅仅是把+变成-,/变成_

1
String encode2 = Base64.getUrlEncoder().encodeToString(input);

2.哈希算法

HMAC

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
public static void hmacMD5_encode() throws NoSuchAlgorithmException, InvalidKeyException {
// 数据加密的时候使用KeyGenerator生成一个SecretKey
KeyGenerator keyGenerator = KeyGenerator.getInstance("HmacMD5");
SecretKey secretKey = keyGenerator.generateKey();
Mac mac = Mac.getInstance("HmacMd5");
mac.init(secretKey);
// 对HelloWorld进行加密
mac.update("HelloWorld".getBytes(StandardCharsets.UTF_8));
byte[] digest = mac.doFinal();
byte[] encoded = secretKey.getEncoded();
System.out.println("SecretKey:\n" + Arrays.toString(encoded));
// SecretKey数组保存为16进制
String keyHex = new BigInteger(1, encoded).toString(16);
System.out.println("SecretKey Hex:\n" + keyHex);
String encode = new BigInteger(1, digest).toString(16);
System.out.println("Encoded Hex:\n" + encode);

// 验证,使用key的数组生成一个SecretKeySpec
// 疑惑:第二个参数无论传递什么值都不影响结果?
SecretKeySpec secretKeySpec = new SecretKeySpec(encoded, "HmacMd5");
mac.init(secretKeySpec);
mac.update("HelloWorld".getBytes(StandardCharsets.UTF_8));
byte[] result = mac.doFinal();
String decodeHex = new BigInteger(1, result).toString(16);
System.out.println("Decoded Hex:\n" + decodeHex);

// 使用16进制的key还原为byte[],应该去掉最前面的符号位0
byte[] keyBytes = new BigInteger(keyHex,16).toByteArray();
if(keyBytes[0] == 0) {
byte[] tempBytes = new byte[keyBytes.length - 1];
System.arraycopy(keyBytes,1,tempBytes,0,tempBytes.length);
keyBytes = tempBytes;
}
SecretKeySpec secretKeySpec1 = new SecretKeySpec(keyBytes, "HmacMd5");
mac.init(secretKeySpec1);
mac.update("HelloWorld".getBytes(StandardCharsets.UTF_8));
byte[] result1 = mac.doFinal();
String decodeHex1 = new BigInteger(1, result1).toString(16);
System.out.println("Decoded Hex:\n" + decodeHex1);

}

对称加密

AES

AES生成128的密文,需要的密钥一定是16bit.

工作模式:ECB
ECB模式下,固定的密钥和明文,每次加密生成的密文都是一样的.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static String aesDecrypt(String key,byte[] input) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchPaddingException, NoSuchAlgorithmException {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
cipher.init(Cipher.DECRYPT_MODE,secretKeySpec);
byte[] decrypt = cipher.doFinal(input);
return new String(decrypt, StandardCharsets.UTF_8);
}

public static String aesEncrypt(String key,byte[] input) throws NoSuchPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
cipher.init(Cipher.ENCRYPT_MODE,secretKeySpec);
byte[] encrypt = cipher.doFinal(input);
return Base64.getEncoder().encodeToString(encrypt);

}

工作模式:CBC
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
public static void main(String[] args) throws Exception {
String key = "1234567890abcdef1234567890abcdef";
String aesEncrypt = aesEncrypt(key, "Hello,World".getBytes(StandardCharsets.UTF_8));
System.out.println("Encrypt:" + aesEncrypt);
String aesDecrypt = aesDecrypt(key, Base64.getDecoder().decode(aesEncrypt));
System.out.println("Decrypt:" + aesDecrypt);
}

public static String aesDecrypt(String key,byte[] input) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException {
// 分割iv和data,iv为前16bit
byte[] iv = new byte[16];
byte[] data = new byte[input.length - 16];
System.arraycopy(input,0,iv,0,16);
System.arraycopy(input,16,data,0,data.length);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE,secretKeySpec,ivParameterSpec);
byte[] decrypt = cipher.doFinal(data);
return new String(decrypt, StandardCharsets.UTF_8);
}

public static String aesEncrypt(String key,byte[] input) throws NoSuchPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
SecureRandom secureRandom = SecureRandom.getInstanceStrong();
byte[] iv = secureRandom.generateSeed(16);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE,secretKeySpec,ivParameterSpec);
// 加密时前16位保存iv,再保存数据
byte[] data = cipher.doFinal(input);
byte[] encrypt = new byte[iv.length + data.length];
System.arraycopy(iv,0,encrypt,0,iv.length);
System.arraycopy(data,0,encrypt,iv.length,data.length);
return Base64.getEncoder().encodeToString(encrypt);

}

出现错误:

1
javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.

这是由于SecureRandom的生成方式不同而导致的错误,可能在window系统上可以运行,在linux系统上不能运行.

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 String aesDecrypt(String key,byte[] input) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchPaddingException, NoSuchAlgorithmException {
Cipher cipher = Cipher.getInstance("AES");
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
// 在linux系统上需要setSeed
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(key.getBytes(StandardCharsets.UTF_8));
keyGenerator.init(128,secureRandom);

// 在window系统上可以直接new SecureRandom
// keyGenerator.init(128,new SecureRandom(key.getBytes(StandardCharsets.UTF_8)));
SecretKey secretKey = keyGenerator.generateKey();
cipher.init(Cipher.DECRYPT_MODE,secretKey);
byte[] decrypt = cipher.doFinal(input);
return new String(decrypt, StandardCharsets.UTF_8);
}

public static String aesEncrypt(String key,byte[] input) throws NoSuchPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
Cipher cipher = Cipher.getInstance("AES");
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
// 在linux系统上需要setSeed
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(key.getBytes(StandardCharsets.UTF_8));
keyGenerator.init(128,secureRandom);

// 在window系统上可以直接new SecureRandom
// keyGenerator.init(128,new SecureRandom(key.getBytes(StandardCharsets.UTF_8)));
SecretKey secretKey = keyGenerator.generateKey();
cipher.init(Cipher.ENCRYPT_MODE,secretKey);
byte[] encrypt = cipher.doFinal(input);
return Base64.getEncoder().encodeToString(encrypt);

}

关于AES的加密方式:
方式1: 密钥只能是16bit,否则报错

1
2
java.security.InvalidKeyException: Invalid AES key length: 9 bytes
at com.sun.crypto.provider.AESCrypt.init(AESCrypt.java:87)
1
2
3
Cipher cipher = Cipher.getInstance("AES");
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
cipher.init(Cipher.DECRYPT_MODE,secretKeySpec);

方式2:密钥不一定要16bit

使用SecretKey.

1
2
3
4
5
6
7
Cipher cipher = Cipher.getInstance("AES");
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(key.getBytes(StandardCharsets.UTF_8));
keyGenerator.init(128,secureRandom);
SecretKey secretKey = keyGenerator.generateKey();
cipher.init(Cipher.ENCRYPT_MODE,secretKey);

或者使用SecretKeySpec也可以.

1
2
3
4
5
6
7
8
Cipher cipher = Cipher.getInstance("AES");
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(key.getBytes(StandardCharsets.UTF_8));
keyGenerator.init(128,secureRandom);
SecretKey secretKey = keyGenerator.generateKey();
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
cipher.init(Cipher.DECRYPT_MODE,secretKeySpec);

密钥交换算法

DH

DH算法不传递密钥,而是使用对方的公钥生成密钥.

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
public class DHTest {
public static void main(String[] args) throws Exception {
Person jim = new Person("Jim");
Person kim = new Person("Kim");

// 各自生成KeyPair(公钥和私钥)
jim.generateKeys();
kim.generateKeys();

// 使用对方的公钥生成secretKey
jim.generateSecretKey(kim.getPublicKey().getEncoded());
kim.generateSecretKey(jim.getPublicKey().getEncoded());

// 检验生成的secretKey是否相同,相同的话以后通信使用这个secretKey
jim.printKey();
kim.printKey();
System.out.println("secretKey equals? " + Arrays.equals(jim.getSecretKey(), kim.getSecretKey()));
}
}



@Data
public class Person {
private String name;
private PublicKey publicKey;
private PrivateKey privateKey;
private byte[] secretKey;


public Person(String name) {
this.name = name;
}

// 生成自己的私钥和公钥
public void generateKeys() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DH");
keyPairGenerator.initialize(512);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
this.privateKey = keyPair.getPrivate();
this.publicKey = keyPair.getPublic();
}

// 根据对方的公钥生成SecretKey
public void generateSecretKey(byte[] receivedPublicKeyBytes) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException {
// 根据byte[]恢复成公钥
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(receivedPublicKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("DH");
PublicKey publicKey = keyFactory.generatePublic(keySpec);

// 生成secretKey
KeyAgreement keyAgreement = KeyAgreement.getInstance("DH");
// 自己的私钥
keyAgreement.init(this.privateKey);
// 对方的公钥
keyAgreement.doPhase(publicKey,true);
// 生成secretKey
this.secretKey = keyAgreement.generateSecret();
}

public void printKey() {
System.out.println("name:" + this.name);
System.out.println("privateKey:" + new BigInteger(1,this.privateKey.getEncoded()));
System.out.println("publicKey:" + new BigInteger(1,this.publicKey.getEncoded()));
System.out.println("secretKey:" + new BigInteger(1,this.secretKey));
}
}

非对称加密

RSA

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
public class RSATest {
public static void main(String[] args) throws IllegalBlockSizeException, NoSuchPaddingException, BadPaddingException, NoSuchAlgorithmException, InvalidKeyException {
Person jim = new Person("Jim");

// 使用jim的公钥加密
String content = "Hello,World";
byte[] encrypt = jim.encrypt(content);
System.out.println("Encrypt Hex:" + new BigInteger(1,encrypt).toString(16));

// 使用jim的私钥解密
byte[] decrypt = jim.decrypt(encrypt);
System.out.println("Decrypt:" + new String(decrypt));
}

static class Person {
private String name;
private PrivateKey privateKey;
private PublicKey publicKey;

public Person(String name) {
this.name = name;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
this.privateKey = keyPair.getPrivate();
this.publicKey = keyPair.getPublic();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}

// 使用公钥加密
public byte[] encrypt(String content) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchPaddingException, NoSuchAlgorithmException {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, this.publicKey);
return cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
}

// 使用私钥解密
public byte[] decrypt(byte[] encrypt) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE,this.privateKey);
return cipher.doFinal(encrypt);
}
}
}

从byte[]恢复公钥和私钥的方法:

1
2
3
4
5
6
7
8
9
byte[] pkData = ...
byte[] skData = ...
KeyFactory kf = KeyFactory.getInstance("RSA");
// 恢复公钥:
X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(pkData);
PublicKey pk = kf.generatePublic(pkSpec);
// 恢复私钥:
PKCS8EncodedKeySpec skSpec = new PKCS8EncodedKeySpec(skData);
PrivateKey sk = kf.generatePrivate(skSpec);

数字签名

数字签名的本质就是用发送方的私钥进行签名.

一般来说,签名的时候并不是直接对原始数据签名,而是对原始数据生成哈希摘要(如果使用RSA算法的话,直接对原始数据进行签名,对原始数据的长度有限制.只有将任意长度的原始消息转换成固定长度的哈希摘要才能保证加密成功),再使用私钥对哈希摘要进行签名.
签名验证的本质就是用发送方的公钥进行解密
用发送方的公钥进行解密,如果能正确解密,说明数据一定是发送方发送的,因为能够使用发送方的公钥正确解密,那一定是拥有发送方私钥加密的,保证了发送方的抗否认性.同时,获取到发送方生成的哈希摘要.再使用同样的哈希方法对发送方发送的原始数据计算出哈希摘要,自己计算的哈希摘要与发送方生成的哈希摘要进行对比,如果摘要一致,说明数据没有被篡改,保证了数据的完整性.

由此可见,私钥就相当于用户身份,公钥是用来给外部验证用户身份的.数字签名可以确保数据完整性和抗否认性.数字签名可以用于:
1.防止伪造|抵赖:如果签名验证成功了,说明数据一定是某一个拥有私钥的发送方发送的,任何人无法伪造,发送方也无法抵赖.
2.检测篡改: 如果签名验证后获取的摘要与自己重新计算的摘要一致,说明数据没有被篡改.

数字签名存在的问题:
1.无法验证发送方的身份.比如,黑客进行了DNS挟持,举个例子,我们使用浏览器访问baidu.com,原本DNS服务器是将baidu.com解析成百度对应的服务器地址的.但是黑客入侵网关修改了DNS服务器,把原本正确的服务器地址修改成了黑客自己的服务器地址,并且黑客伪造了自己的私钥和公钥,当你用浏览器访问时,黑客的服务器会返回给你公钥,浏览器进行签名验证,签名验证依然通过,但这个公钥已经不是baidu.com的公钥了,而是黑客伪造的公钥,以后黑客就可以伪造成baidu.com与浏览器通信.(数字证书可以解决这个问题,因为数字证书引入了CA认证,由CA去验证发送方的身份).

常用的签名算法有:
1.MD5withRSA
2.SHA1withRSA
3.SHA256withRSA

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
public class Main {
public static void main(String[] args) throws GeneralSecurityException {
// 生成RSA公钥/私钥:
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA");
kpGen.initialize(1024);
KeyPair kp = kpGen.generateKeyPair();
PrivateKey sk = kp.getPrivate();
PublicKey pk = kp.getPublic();

// 待签名的消息:
byte[] message = "Hello, I am Bob!".getBytes(StandardCharsets.UTF_8);

// 用私钥签名:
Signature s = Signature.getInstance("SHA1withRSA");
s.initSign(sk);
s.update(message);
byte[] signed = s.sign();
System.out.println(String.format("signature: %x", new BigInteger(1, signed)));

// 用公钥验证:
Signature v = Signature.getInstance("SHA1withRSA");
v.initVerify(pk);
v.update(message);
boolean valid = v.verify(signed);
System.out.println("valid? " + valid);
}
}

数字证书

数字签名能保证数据的完整性和抗否定性.但是却不能验证公钥的身份.而数字证书的出现,就是为了证明公钥持有者的身份.

一般会在证书中包含以下几类信息:
1.公钥信息
2.拥有者身份信息
3.CA信息,有效时间,证书序列号等
4.CA对该证书的数字签名

证书拥有者通过该证书,即可以向系统或者其他用户证明身份,从而获得对方信任并授权使用某些敏感服务.

证书的本质就是CA用自己的私钥对公钥持有者的公钥进行数字签名.其他用户只要用CA的公钥去验证证书上的数字签名,就可以证明公钥持有者的身份.

数字证书能避免中间人攻击.

HTTPS

在将数据提交给HTTP层之后,数据会经过用户电脑,wifi路由器,运营商和目标服务器,在这中间的每个环节中,数据都有可能被窃取或篡改.比如用户电脑被黑客装了恶意软件.HTTP传输的内容很容易被中间人窃取|伪造|篡改,通常我们把这种攻击方式称为中间人攻击.

我们可以在HTTP和TCP之间插入一个安全层,所有经过安全层的数据都会被加密或者解密.

过程:
1.浏览器请求服务器数据
2.服务器用自己的私钥加密网页内容后,连同数字证书一起发送给浏览器
3.数字证书是经过CA的私钥加密的,浏览器用操作系统自带的Root CA来验证服务器的证书是否有效(浏览器查看证书管理器的受信任的根证书颁发机构列表中是否存在解开数字证书的公钥?)
可能会发生3种情况:
a.如果数字证书记载的网站与当前访问的网站不一样,浏览器会发出警告
b.如果数字证书不是受信任的机构办法的,浏览器会发出另外一种警告
c.如果数字证书是可靠的,浏览器就可以使用数字证书中携带的服务器的公钥加密一个随机的AES密钥发送给服务器,服务器会用自己的私钥解密获得AES密钥,在随后的通讯中使用AES对称加密(因为AES对称加密解密的速度快)

一毛也是爱~
Kim.Zhang 微信支付

微信支付

shiro.md
  • 文章目录
  • 站点概览
Kim.Zhang

Kim.Zhang

且行且珍惜
94 日志
12 分类
42 标签
E-Mail Weibo
  1. 1. 1.编码
    1. 1.1. URL编码
    2. 1.2. Base64编码
  2. 2. 2.哈希算法
    1. 2.1. HMAC
  3. 3. 对称加密
    1. 3.0.1. AES
  • 4. 密钥交换算法
    1. 4.1. DH
  • 5. 非对称加密
    1. 5.1. RSA
  • 6. 数字签名
  • 7. 数字证书
  • 8. HTTPS
  • 粵ICP备19091267号 © 2019 – 2022 Kim.Zhang | 629k | 9:32
    本站总访问量 4 次 | 有 309 人看我的博客啦 |
    博客全站共176.7k字
    载入天数...载入时分秒...
    0%