一、AES 加密
对称加密方式,加解密用同一秘钥,速度快,效率高,但是存在密钥交换问题。
密钥交换问题:通过RSA+AES混合加密可以使数据传输更安全些:
移动端生成AES密钥,将要传输的数据通过AES加密后上传到服务端,同时将该AES密钥用RSA公钥加密并上传到服务端;在服务端获得通过RSA公钥加密后的AES密钥和通过AES密钥加密后的数据,用RSA私钥解密得到正确的AES密钥,使用AES密钥对数据源解密,获取明文数据。
1. AES密钥
- 支持三种密钥长度:128位、192位、256位,大家经常说的AES128、AES256就是指不同的密钥长度。
- 不同的密钥长度意味着AES加密的轮数不同,1287位加密10轮,192位加密12轮,256位加密14轮,从安全性角度来讲,256位安全性最高,但是128位因为加密轮数少,所以性能更好一些。
这里的长度位是bit,char类型是2字节,一个字节是8位,所以密钥字符串长度为16,就是256位,安全性最高。
2. 填充
AES不是将拿到的明文一次性加密,而是分组加密,就是先将明文切分成长度相等的块,每块大小128bit,再对每一小块进行加密。那么问题就来了,并不是所有的原始明文串能被等分成128bit,例如原串大小200bit,那么第二个块只有72bit,所以就需要对第二个块进行填充处理,让第二个块的大小达到128bit。常见的填充模式有以下三种:
2.1 NoPadding
不进行填充,要求原始加密串大小必须是 128bit 的整数倍
2.2 PKCS5Padding
假设块大小8字节,如果这个块跟8字节还差n个字节,那么就在原始块填充n,直到满8字节。例:块{1,2,3},跟8字节差了5个字节,那么补全后的结果{1,2,3,5,5,5,5,5}后面是五个5,块{1,2,3,4,5,6,7}跟8字节差了1个字节,那么补全后就是{1,2,3,4,5,6,7,1},就是补了一个1。
如果恰好8字节又选择了PKCS5Padding填充方式呢?块{1,2,3…8}填充后变成{1,2,3…8,8…8},原串后面被补了8个8,这样做的原因是方便解密,只需要看最后一位就能算出原块的大小是多少。
2.3 PKCS7Padding
跟PKCS5Padding的填充方式一样,不同的是,PKCS5Padding只是对8字节的进行填充,PKCS7Padding可以对1~256字节大小的block进行填充。
3. 模式
AES有多种加密模式,包括:ECB,CBC,CTR,OCF,CFB,最常见的还是 ECB 和 CBC 模式。
3.1 ECB模式
最简单的一种加密模式,每个块进行独立加密,块与块之间加密互不影响,这样就能并行,效率高。
虽然这样加密很简单,但是不安全,如果两个块的明文一模一样,那么加密出来的东西也一模一样。
3.2 CBC模式
CBC模式中引入了一个新的概念,初始向量iv。iv的作用就是为了防止同样的明文块被加密成同样的内容。原理是第一个明文块跟初始向量做异或后加密,第二个块跟第一个密文块做异或再加密,依次类推,避免了同样的块被加密成同样的内容。
所以跟第三方对接的时候,如果对面说他们用aes加密,务必对他们发起灵魂三问:
- AES用的什么模式?
- AES的填充方式是什么?有的第三方不用标准的填充方式,令人费解,所以一定要问清楚
- 设置的初始向量是什么?(如果对方说他们用cbc)
工具类如下:
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
public class AESUtil {
/**
* 算法名称
**/
public static final String KEY_ALGORITHM = "AES";
/**
* 算法名称/加密模式/填充方式
**/
public static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
/**
* 编码
**/
public static final String CHARSET_NAME = "UTF-8";
public static void main(String[] args) throws Exception {
String source = "15500000000";
System.out.println("原文: " + source);
String key = "kT4kMc51VcD/DmKw";
String encryptData = encrypt(source, key);
System.out.println("加密后: " + encryptData);
String decryptData = decrypt(encryptData, key);
System.out.println("解密后: " + decryptData);
}
/**
* 加密数据
*
* @param data 待加密数据
* @param key 密钥
* @return 加密后的数据
*/
public static String encrypt(String data, String key) throws Exception {
// 生成密钥key对象
Key secretKey = new SecretKeySpec(key.getBytes(CHARSET_NAME), KEY_ALGORITHM);
IvParameterSpec iv = new IvParameterSpec(key.getBytes(CHARSET_NAME));
// 实例化Cipher对象,它用于完成实际的加密操作
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
// 初始化Cipher对象,设置为加密模式
cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
byte[] results = cipher.doFinal(data.getBytes(CHARSET_NAME));
// 执行加密操作。加密后的结果通常都会用Base64编码进行传输
return Base64.getEncoder().encodeToString(results);
}
/**
* 解密数据
*
* @param data 待解密数据
* @param key 密钥
* @return 解密后的数据
*/
public static String decrypt(String data, String key) throws Exception {
Key secretKey = new SecretKeySpec(key.getBytes(CHARSET_NAME), KEY_ALGORITHM);
IvParameterSpec iv = new IvParameterSpec(key.getBytes(CHARSET_NAME));
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
return new String(cipher.doFinal(Base64.getDecoder().decode(data)));
}
}
二、RSA 加密
非对称加密方式。需要两个密钥,一个公钥一个私钥(公钥置于移动端,私钥置于服务端),使用其中一把密钥加密后的明文,只有对应的密钥才能解的开。
rsa可以用来做加密或者做签名,两者性质不一样。
1. 签名和加密的区别
签名的作用是让接受方验证你传过去的数据没有被篡改;加密的作用是保证数据不被窃取。
2. 加密
当移动端向服务端传输重要数据的时候,可以用公钥对数据进行加密再传输;到服务端,服务端使用私钥对该加密过的数据进行解密,获得对应数据。反之,服务端通过私钥对要传输的数据加密,移动端获得加密后的数据通过公钥解密获得对应数据。
工具类如下:
import org.apache.commons.codec.binary.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
public class RSAEncryptUtil {
/**
* 生成公钥、私钥对(keysize=1024)
*
* @return
*/
public KeyPairInfo getKeyPair() {
return getKeyPair(1024);
}
/**
* 生成公钥、私钥对
*
* @param keySize
* @return
*/
public KeyPairInfo getKeyPair(int keySize) {
try {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
// 初始化密钥对生成器,密钥大小一般要大于1024位,
keyPairGen.initialize(keySize);
// 生成一个密钥对,保存在keyPair中
KeyPair keyPair = keyPairGen.generateKeyPair();
// 得到私钥
RSAPrivateKey oraprivateKey = (RSAPrivateKey) keyPair.getPrivate();
// 得到公钥
RSAPublicKey orapublicKey = (RSAPublicKey) keyPair.getPublic();
KeyPairInfo pairInfo = new KeyPairInfo(keySize);
//公钥
byte[] publicKeybyte = orapublicKey.getEncoded();
String publicKeyString = Base64.encodeBase64String(publicKeybyte);
pairInfo.setPublicKey(publicKeyString);
//私钥
byte[] privateKeybyte = oraprivateKey.getEncoded();
String privateKeyString = Base64.encodeBase64String(privateKeybyte);
pairInfo.setPrivateKey(privateKeyString);
return pairInfo;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
/**
* 获取公钥对象
*
* @param publicKeyBase64
* @return
* @throws InvalidKeySpecException
* @throws NoSuchAlgorithmException
*/
public PublicKey getPublicKey(String publicKeyBase64)
throws InvalidKeySpecException, NoSuchAlgorithmException {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec publicpkcs8KeySpec =
new X509EncodedKeySpec(Base64.decodeBase64(publicKeyBase64));
PublicKey publicKey = keyFactory.generatePublic(publicpkcs8KeySpec);
return publicKey;
}
/**
* 获取私钥对象
*
* @param privateKeyBase64
* @return
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
*/
public PrivateKey getPrivateKey(String privateKeyBase64)
throws NoSuchAlgorithmException, InvalidKeySpecException {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec privatekcs8KeySpec =
new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyBase64));
PrivateKey privateKey = keyFactory.generatePrivate(privatekcs8KeySpec);
return privateKey;
}
/**
* 使用公钥加密
*
* @param content 待加密内容
* @param publicKeyBase64 公钥 base64 编码
* @return 经过 base64 编码后的字符串
*/
public String encipher(String content, String publicKeyBase64) {
return encipher(content, publicKeyBase64, -1);
}
/**
* 使用公钥加密(分段加密)
*
* @param content 待加密内容
* @param publicKeyBase64 公钥 base64 编码
* @param segmentSize 分段大小,一般小于 keySize/8(段小于等于0时,将不使用分段加密)
* @return 经过 base64 编码后的字符串
*/
public String encipher(String content, String publicKeyBase64, int segmentSize) {
try {
PublicKey publicKey = getPublicKey(publicKeyBase64);
return encipher(content, publicKey, segmentSize);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 分段加密
*
* @param ciphertext 密文
* @param key 加密秘钥
* @param segmentSize 分段大小,<=0 不分段
* @return
*/
public String encipher(String ciphertext, Key key, int segmentSize) {
try {
// 用公钥加密
byte[] srcBytes = ciphertext.getBytes();
// Cipher负责完成加密或解密工作,基于RSA
Cipher cipher = Cipher.getInstance("RSA");
// 根据公钥,对Cipher对象进行初始化
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] resultBytes = null;
if (segmentSize > 0)
resultBytes = cipherDoFinal(cipher, srcBytes, segmentSize); //分段加密
else
resultBytes = cipher.doFinal(srcBytes);
String base64Str = Base64.encodeBase64String(resultBytes);
return base64Str;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 分段大小
*
* @param cipher
* @param srcBytes
* @param segmentSize
* @return
* @throws IllegalBlockSizeException
* @throws BadPaddingException
* @throws IOException
*/
private byte[] cipherDoFinal(Cipher cipher, byte[] srcBytes, int segmentSize)
throws IllegalBlockSizeException, BadPaddingException, IOException {
if (segmentSize <= 0)
throw new RuntimeException("分段大小必须大于0");
ByteArrayOutputStream out = new ByteArrayOutputStream();
int inputLen = srcBytes.length;
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段解密
while (inputLen - offSet > 0) {
if (inputLen - offSet > segmentSize) {
cache = cipher.doFinal(srcBytes, offSet, segmentSize);
} else {
cache = cipher.doFinal(srcBytes, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * segmentSize;
}
byte[] data = out.toByteArray();
out.close();
return data;
}
/**
* 使用私钥解密
*
* @param contentBase64 待加密内容,base64 编码
* @param privateKeyBase64 私钥 base64 编码
* @return
* @segmentSize 分段大小
*/
public String decipher(String contentBase64, String privateKeyBase64) {
return decipher(contentBase64, privateKeyBase64, -1);
}
/**
* 使用私钥解密(分段解密)
*
* @param contentBase64 待加密内容,base64 编码
* @param privateKeyBase64 私钥 base64 编码
* @return
* @segmentSize 分段大小
*/
public String decipher(String contentBase64, String privateKeyBase64, int segmentSize) {
try {
PrivateKey privateKey = getPrivateKey(privateKeyBase64);
return decipher(contentBase64, privateKey, segmentSize);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 分段解密
*
* @param contentBase64 密文
* @param key 解密秘钥
* @param segmentSize 分段大小(小于等于0不分段)
* @return
*/
public String decipher(String contentBase64, Key key, int segmentSize) {
try {
// 用私钥解密
byte[] srcBytes = Base64.decodeBase64(contentBase64);
// Cipher负责完成加密或解密工作,基于RSA
Cipher deCipher = Cipher.getInstance("RSA");
// 根据公钥,对Cipher对象进行初始化
deCipher.init(Cipher.DECRYPT_MODE, key);
byte[] decBytes = null;//deCipher.doFinal(srcBytes);
if (segmentSize > 0)
decBytes = cipherDoFinal(deCipher, srcBytes, segmentSize); //分段加密
else
decBytes = deCipher.doFinal(srcBytes);
String decrytStr = new String(decBytes);
return decrytStr;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 秘钥对
*/
public class KeyPairInfo {
public KeyPairInfo(int keySize) {
setKeySize(keySize);
}
public KeyPairInfo(String publicKey, String privateKey) {
setPrivateKey(privateKey);
setPublicKey(publicKey);
}
String privateKey;
String publicKey;
int keySize = 0;
public String getPrivateKey() {
return privateKey;
}
public void setPrivateKey(String privateKey) {
this.privateKey = privateKey;
}
public String getPublicKey() {
return publicKey;
}
public void setPublicKey(String publicKey) {
this.publicKey = publicKey;
}
public int getKeySize() {
return keySize;
}
public void setKeySize(int keySize) {
this.keySize = keySize;
}
}
}