编码算法
URL 编码
目的:
在使用 URL 向服务器发送数据时,由于很多服务器只能识别 ASCII 字符,但如果URL中包含中文、日文这些非 ASCII 字符时,就需要对其编码后,再发送给服务器。
注意:
在和其他语言进行数据交互时,如果使用到了 URL 编码算法,需要特别注意两种语言的差异,下面便是实际遇到的一种:
前端传递了一个 URL 编码的用户名s,后端使用不同的实现完成 URL 解码,得到不同的结果s1,s2(正确):
(urlCodec:org.apache.commons.codec.net.URLCodec)
对正确的用户名去编码、解码,得到:
结论:Java 直接使用 URLCodec,就可以避免不同语言的解码失败问题。
Base64 编码
完成的功能:
Base64 编码是对二进制数据进行编码,表示成文本格式,即服务器通用的(字母、数字、+、/、=) 字符,也由于该特性,Base64 编码后,数据长度增加 1/3 。
在项目中,将报文进行压缩、加密后,最后一步必然是使用 Base64 编码,因为 Base64 编码的字符串,更适合不同平台、不同语言的传输。
Java用例:
String src="abc^???-)";
String encode = Base64.getEncoder().encodeToString(src.getBytes("utf-8"));
System.out.println(encode); //YWJjXj8/Py0p
byte[] decode = Base64.getDecoder().decode(encode);
String decodeSrc = new String(decode);
System.out.println(decodeSrc); //abc^???-)
对称加密算法
对称加密,是为了解决数据传输过程中,被第三方窃取的问题。发送方通过约定的密钥加密,接收方接受到密文后,使用密钥解密便得到明文。
在整个过程中,密钥的安全性,显的比较重要。密钥的确定有两种方式,一是发送、接受方提前约定好,配置在代码中,二是通过传输临时约定密钥,该种方式需要考虑中间人攻击的情况。
常用的对称加密算法:
算法 | 密钥长度 | 工作模式 | 填充模式 |
---|---|---|---|
DES | 56/64 | ECB/CBC/PCBC/CTR/… | NoPadding/PKCS5Padding/… |
AES | 128/192/256 | ECB/CBC/PCBC/CTR/… | NoPadding/PKCS5Padding/PKCS7Padding/… |
IDEA | 128 | ECB | PKCS5Padding/PKCS7Padding/… |
DES短时间内可被破解,故不再推荐使用
提前约定密钥的 AES 对称加密
从上述的表格中可以看到,aes 的密钥可以为三种固定的长度,16、24、32个字符的字符串,其余长度的密钥均会被各种语言平台处理为对应的长度。
AES/ECB/PKCS5Padding 加密用例:
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
public class AES {
public static void main(String[] args) {
String key="1234567890123456";
try {
String encrypt = encrypt("abccccccccabc", key);
System.out.println(encrypt);//16:EiiaUaAym+SZW2mhCchsHw== 24:D2Hdlvx19upLOQHjr9j8WQ== 32
String decrypt = decrypt(encrypt, key);
System.out.println(decrypt);
} catch (Exception e) {
e.printStackTrace();
}
}
// 加密
public static String encrypt(String sSrc, String sKey) throws Exception {
if (sKey == null) {
System.out.print("Key为空null");
return null;
}
SecretKeySpec skeySpec = new SecretKeySpec(sKey.getBytes("utf-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");//"算法/模式/补码方式"
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(sSrc.getBytes("utf-8"));
return Base64.encodeBase64String(encrypted);//此处使用BASE64做转码功能,同时能起到2次加密的作用。
}
//解密
public static String decrypt(String sSrc, String sKey) throws Exception {
// 判断Key是否正确
if (sKey == null) {
System.out.print("Key为空null");
return null;
}
SecretKeySpec skeySpec = new SecretKeySpec(sKey.getBytes("utf-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] encrypted1 = Base64.decodeBase64(sSrc);//先用base64解密
byte[] original = cipher.doFinal(encrypted1);//再用aes解密
String originalString = new String(original, "utf-8");
return originalString;
}
}
非对称加密算法RSA
概念:
所谓非对称加密,就是加密和解密使用的不是相同的密钥。
公私钥是成对使用的。公钥可以公开给他人使用,私钥非公开保存。可以用私钥去解密他人用公钥加密后的文件内容。
缺点:
相比于对称加密而言,非对称加密算法时间复杂度高,一般需要两者配合来使用,实现高效且相对安全的数据传输。
需要注意的是:
在 RSA 加密时,对同一个明文加密,每次都会得到不同的密文。由此可得,解密时,不同的密文,可能得到相同的明文。
RSA 实战工具:
/**
* 功能:生成公私钥对;按字符串形式存储公私钥对;用存储的公私钥对进行加解密字符串
*/
public class RSA {
public static void main(String[] args) throws Exception {
// 创建公钥/私钥对:
Person alice = new Person();
alice.generatorPkAndSk();
//得到字符串类型的公私钥,用于保存
String publicKeyForSave = alice.getPublicKeyForSave();
String privateKeyForSave = alice.getPrivateKeyForSave();
// 明文:
String plain = "Hello, encrypt use RSA中文 ";
String encrypt = alice.encrypt(plain, publicKeyForSave);
//注意:同一个明文加密,每次都会得到不同的密文
System.out.println(encrypt);
String decrypt = alice.decrypt(encrypt, privateKeyForSave);
System.out.println(decrypt);
}
}
class Person {
// 私钥:
PrivateKey sk;
// 公钥:
PublicKey pk;
public void generatorPkAndSk() throws GeneralSecurityException {
// 生成公钥/私钥对:
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA");
kpGen.initialize(1024);
KeyPair kp = kpGen.generateKeyPair();
this.sk = kp.getPrivate();
this.pk = kp.getPublic();
}
// 私钥导出存储用
public String getPrivateKeyForSave() {
byte[] encoded = this.sk.getEncoded();
return Base64.getEncoder().encodeToString(encoded);
}
// 把公钥导出存储
public String getPublicKeyForSave() {
byte[] encoded = this.pk.getEncoded();
return Base64.getEncoder().encodeToString(encoded);
}
/**
* 用公钥加密
* @param source 待加密的明文
* @param publicKey rsa公钥
* @return
*/
public String encrypt(String source,String publicKey) throws Exception {
//生成公钥
byte[] keyByte = Base64.getDecoder().decode(publicKey);
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(keyByte);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey realPublicKey = keyFactory.generatePublic(x509EncodedKeySpec);
//使用公钥加密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, realPublicKey);
byte[] bytes = cipher.doFinal(source.getBytes("utf-8"));
//对返回的密文编码,方便传输存储
String encryptSource = Base64.getEncoder().encodeToString(bytes);
return encryptSource;
}
/**
* 用私钥解密
* @param source 待解密的密文
* @param privateKey rsa私钥
* @return
*/
public String decrypt(String source,String privateKey) throws Exception {
//生成私钥
byte[] keyByte = Base64.getDecoder().decode(privateKey);
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(keyByte);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey realPrivateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
//使用私钥解密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, realPrivateKey);
byte[] decodeSource = Base64.getDecoder().decode(source);
byte[] decryptBytes = cipher.doFinal(decodeSource);
return new String(decryptBytes);
}
}
签名算法
数字签名的目的是为了确认某个信息确实是由某个发送方发送的,其他人都不可能伪造消息,并且,发送方也不能抵赖。
签名算法使用非对称加密中公私钥对,私钥加密得到的密文实际上就是数字签名,要验证这个签名是否正确,只能用私钥持有者的公钥进行解密验证。私钥就相当于用户身份,而公钥用来给外部验证用户身份。
常用数字签名算法有:
- MD5withRSA
- SHA1withRSA
- SHA256withRSA