我们先来了解一下什么是对称加密和非对称加密,以及两者的优缺点
对称加密
-
使用同一个密钥对消息进行加密解密
-
优点:加密和解密的速度快,适合于数据量大的加解密
-
缺点:密钥在网络传输中可能被泄露,因此安全性相对较低。另外,每对用户每次使用对称加密算法时,都需要使用其他人不知道的独一无二的密钥,这会使得收、发双方所拥有的钥匙数量巨大,密钥管理成为双方的负担
-
常用的对称加密算法有 DES、3DES、AES、TDEA、Blowfish、RC2、RC4 和 RC5 等。
非对称加密
-
使用一对密钥进行加密解密,一般公钥用于加密,私钥用于解密
-
优点:安全性高,因为即使公钥被其他人获取,没有私钥也无法解密
-
缺点:加密和解密的速度相对较慢
-
非对称加密在 TLS(或 SSL)协议中得到了广泛应用,HTTPS就是使用的该加密方式
-
常见的非对称加密算法有 RSA和ECC等。
代码实现
下面我们将从代码层面去实现非对称加密
(我使用的是SpringBoot+Vue2基于RSA实现非对称加密)
前端
1.下载相关依赖
npm install jsencrypt
原生js通过该方式引入
<script src="https://cdn.bootcdn.net/ajax/libs/jsencrypt/3.2.1/jsencrypt.min.js"></script>
2.编写相关的工具类
import JSEncrypt from 'jsencrypt';
// 密钥对生成 http://web.chacuo.net/netrsakeypair
const publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCj+WHUA1lj2aC9evQuIe/NeIUTgUvvzcZ5EO+msm60tLMdwTag4reQJ/kVLt+41tgHBTBpoxYDBLRDuGsWCbjXvzlJw1rUEgPI+jHwMkE8FrL2HNY1Opz0H3UV7PKhggGV1f2LEia7O08VLY0BbRXcKm9o93flAKt9O9y48nzTDwIDAQAB"
export default {
// 加密
encrypt(txt) {
const encryptor = new JSEncrypt()
encryptor.setPublicKey(publicKey) // 设置公钥
return encryptor.encrypt(txt) // 对需要加密的数据进行加密
},
// 解密
decrypt(txt) {
const encryptor = new JSEncrypt()
encryptor.setPrivateKey(privateKey)
return encryptor.decrypt(txt)
}
}
3.在代码中修改需要加密的相关逻辑(在这里编写了一个简单的登录请求)
<script>
import api from '../api/api'
import encryptPassword from '../utils/encryptPassword'
export default {
data() {
return {
username: 'root',
password: '123456',
};
},
methods: {
login() {
// 使用axios发送登录请求
try {
api.post('/user/login', {
username: this.username,
password: encryptPassword.encrypt(this.password),
})
.then(response => {
const data = response.data;
if (data.code === -1 && data.msg === '用户名或密码错误') {
alert('用户名或密码错误');
} else {
const token = data.data.token
const sessionId = data.data.sessionId
localStorage.setItem('token', token);
localStorage.setItem('sessionId', sessionId);
this.$router.push('/dashbord');
}
})
}
catch (error) {
// 处理登录失败的情况
console.error(error);
};
}
},
};
</script>
至此,前端代码已经处理完毕
后端
后端代码编写和前端类似
1.引入相关依赖
<!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
2.编写一个RsaUtils工具类
package online.yuuu.fdj.utils;
import org.apache.commons.codec.binary.Base64;
import org.springframework.util.ObjectUtils;
import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
* @author Yuuu
* @description Rsa 工具类,公钥私钥生成,加解密
**/
public class RsaUtils {
private static final String SRC = "yuuu";
public static void main(String[] args) throws Exception {
System.out.println("\n");
RsaKeyPair keyPair = generateKeyPair();
System.out.println("公钥:" + keyPair.getPublicKey());
System.out.println("私钥:" + keyPair.getPrivateKey());
System.out.println("\n");
test1(keyPair);
System.out.println("\n");
test2(keyPair);
System.out.println("\n");
}
/**
* 公钥加密私钥解密
*/
private static void test1(RsaKeyPair keyPair) throws Exception {
System.out.println("***************** 公钥加密私钥解密开始 *****************");
String text1 = encryptByPublicKey(keyPair.getPublicKey(), RsaUtils.SRC);
String text2 = decryptByPrivateKey(keyPair.getPrivateKey(), text1);
System.out.println("加密前:" + RsaUtils.SRC);
System.out.println("加密后:" + text1);
System.out.println("解密后:" + text2);
if (RsaUtils.SRC.equals(text2)) {
System.out.println("解密字符串和原始字符串一致,解密成功");
} else {
System.out.println("解密字符串和原始字符串不一致,解密失败");
}
System.out.println("***************** 公钥加密私钥解密结束 *****************");
}
/**
* 私钥加密公钥解密
* @throws Exception /
*/
private static void test2(RsaKeyPair keyPair) throws Exception {
System.out.println("***************** 私钥加密公钥解密开始 *****************");
String text1 = encryptByPrivateKey(keyPair.getPrivateKey(), RsaUtils.SRC);
String text2 = decryptByPublicKey(keyPair.getPublicKey(), text1);
System.out.println("加密前:" + RsaUtils.SRC);
System.out.println("加密后:" + text1);
System.out.println("解密后:" + text2);
if (RsaUtils.SRC.equals(text2)) {
System.out.println("解密字符串和原始字符串一致,解密成功");
} else {
System.out.println("解密字符串和原始字符串不一致,解密失败");
}
System.out.println("***************** 私钥加密公钥解密结束 *****************");
}
/**
* 公钥解密
*
* @param publicKeyText 公钥
* @param text 待解密的信息
* @return /
* @throws Exception /
*/
public static String decryptByPublicKey(String publicKeyText, String text) throws Exception {
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, publicKey);
byte[] result = cipher.doFinal(Base64.decodeBase64(text));
return new String(result);
}
/**
* 私钥加密
*
* @param privateKeyText 私钥
* @param text 待加密的信息
* @return /
* @throws Exception /
*/
public static String encryptByPrivateKey(String privateKeyText, String text) throws Exception {
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
byte[] result = cipher.doFinal(text.getBytes());
return Base64.encodeBase64String(result);
}
/**
* 私钥解密
*
* @param privateKeyText 私钥
* @param text 待解密的文本
* @return /
* @throws Exception /
*/
public static String decryptByPrivateKey(String privateKeyText, String text) throws Exception {
if(ObjectUtils.isEmpty(text)){
return null;
}
PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] result = cipher.doFinal(Base64.decodeBase64(text));
return new String(result);
}
/**
* 公钥加密
*
* @param publicKeyText 公钥
* @param text 待加密的文本
* @return /
*/
public static String encryptByPublicKey(String publicKeyText, String text) throws Exception {
X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec2);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] result = cipher.doFinal(text.getBytes());
return Base64.encodeBase64String(result);
}
/**
* 构建RSA密钥对
*
* @return /
* @throws NoSuchAlgorithmException /
*/
public static RsaKeyPair generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
String publicKeyString = Base64.encodeBase64String(rsaPublicKey.getEncoded());
String privateKeyString = Base64.encodeBase64String(rsaPrivateKey.getEncoded());
return new RsaKeyPair(publicKeyString, privateKeyString);
}
/**
* RSA密钥对对象
*/
public static class RsaKeyPair {
private final String publicKey;
private final String privateKey;
public RsaKeyPair(String publicKey, String privateKey) {
this.publicKey = publicKey;
this.privateKey = privateKey;
}
public String getPublicKey() {
return publicKey;
}
public String getPrivateKey() {
return privateKey;
}
}
}
3.修改接口
package online.yuuu.fdj.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.api.IErrorCode;
import com.baomidou.mybatisplus.extension.api.R;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import online.yuuu.fdj.entity.User;
import online.yuuu.fdj.entity.vo.UserInfo;
import online.yuuu.fdj.mapper.UserMapper;
import online.yuuu.fdj.service.UserService;
import online.yuuu.fdj.utils.RsaUtils;
import online.yuuu.fdj.utils.TokenUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
/**
* 用户信息表(User)表服务实现类
*
* @author Yuuu
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
private static final String PRIVATE_KEY = "MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAKP5YdQDWWPZoL169C4h7814hROBS+/NxnkQ76aybrS0sx3BNqDit5An+RUu37jW2AcFMGmjFgMEtEO4axYJuNe/OUnDWtQSA8j6MfAyQTwWsvYc1jU6nPQfdRXs8qGCAZXV/YsSJrs7TxUtjQFtFdwqb2j3d+UAq3073LjyfNMPAgMBAAECgYEAl33bSAkgGQQDYYuq9MQdzPM/31lhUtgGOevPvW60oPchVKghVFqhxOlpUozP3PGA/Ftq8u3ddERwVvCW3vF0d0MlE2MMAs/iqN0riYjep38sSYyFniCtFtaUu8Ukt8A7KzQvAx2eZ5O2S8jV7WXt5yzg2IKmE4cogp/2POP7P6ECQQDe2enNbruDsIJKEhBRWnhSoTeRTZuYZZEWbygcR7VojRsEY/t7dBWwNtB3S+uRwGNTmuushT0pU1Mk2o0at4tVAkEAvF1z+/IO3Z7R2/WQz/AMRxbMSo944caVo1CjGyFJd7pqAsEWcaMDxOjh7nI9XUGhHL63sVcfL3TxAlrjmfkM0wJBALYfEpLf9wDVZHUSNCG4NPMaa+YkrY8rGhpidz49YET0ZGr/Jsuqf/sFoPKNqhPwTXxK9GWGcghu31kT/xMhgw0CQHeQHjHa+KKlz/F9kvjaFwD8sJnXLdSp14u9gHqVC2wq8GVlKJji716Kr/ZJnzfVk62KRVEUyt+1q9Fh3bx7QC8CQQCYlnpD3fsbHNGI8mlIz84nGleysE2cr8zxHpKlyc8hxrjGVT6Crct8o7i4Youa/UUMK09XEl8YflKEln+TOj/o";
@Override
public R login(User user, HttpServletRequest request) {
User one = getOne(new LambdaQueryWrapper<User>().eq(User::getUsername, user.getUsername()));
try {
String realPassword = RsaUtils.decryptByPrivateKey(PRIVATE_KEY,user.getPassword());
if ( one == null || !one.getPassword().equals(realPassword)) {
return R.failed("用户名或密码错误");
}
} catch (Exception e) {
throw new RuntimeException(e);
}
String token = TokenUtil.generateToken(user.getUsername());
UserInfo userInfo = new UserInfo();
userInfo.setId(one.getId());
userInfo.setUsername(user.getUsername());
userInfo.setToken(token);
String sessionId = TokenUtil.storeTokenInSession(request, token);
userInfo.setSessionId(sessionId);
return R.ok(userInfo);
}
}
ok,我们可以验证一下是否完成了加解密
启动一下前后端项目
验证效果
我们可以看到,前端发送的数据已经成功加密
后端接收到的也是加密后的数据
那么我们看一下后端解密的效果,解密也是成功实现了!
题外话
正常企业开发,除了在接发请求时进行数据加密外,还会对入库的数据进行加密,此时的加密就是不可逆的
String realPassword = RsaUtils.decryptByPrivateKey(PRIVATE_KEY,user.getPassword());
// 拿到真实密码后,使用自带的md5加密算法进行加盐
String salt = DigestUtils.md5Hex(realPassword);
// 将(密码 + 盐)再进行加密
String password = DigestUtils.md5Hex(realPassword + salt);
// 加密后存入数据库
one.setPassword(password);
不可逆的加密算法主要包括以下几种:
-
MD5:MD5是一种广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(通常用32字符的十六进制数表示)。然而,MD5被认为在许多安全性方面都存在弱点,因此不建议用于密码加密。
-
HMAC:HMAC(Hash-based Message Authentication Code)是一种基于密钥的哈希算法认证协议。消息认证是用于验证消息的完整性以及保护消息的真实性。
-
SHA系列:SHA(Secure Hash Algorithm)是一种安全哈希算法,包括SHA-1、SHA-224、SHA-256、SHA-384、SHA-512等。其中,SHA-224、SHA-256、SHA-384和SHA-512可以统称为SHA2加密算法。SHA加密算法的安全性要比MD5更高,而SHA2加密算法比SHA1的要高。
这些不可逆加密算法通常用于用户密码加密,其验证过程就是通过比较两个加密后的字符串是否一样来确认身份的。然而,这些加密算法并非绝对安全,例如MD5已经被证明存在安全漏洞,因此在处理敏感信息时,应选择适当的加密算法和策略。在实际应用中,通常会结合对称加密和非对称加密算法,以提高数据的安全性。
我们公司的项目就是先RAS解密,后SHA单向加密
谢谢大家的观看