每日一例 | java常用的加密算法實(shí)現(xiàn)

前言
日常開(kāi)發(fā)中,傳輸敏感數(shù)據(jù),或者登陸、權(quán)限校驗(yàn)的時(shí)候,我們經(jīng)常要對(duì)數(shù)據(jù)進(jìn)行加密解密,今天我們就來(lái)看下如何用java來(lái)實(shí)現(xiàn)這些常用的加密算法。
常用的加密算法分為兩種,“對(duì)稱(chēng)式”和“非對(duì)稱(chēng)式”。
對(duì)稱(chēng)式加密技術(shù)
對(duì)稱(chēng)式加密就是加密和解密使用同一個(gè)密鑰,通常稱(chēng)之為“Session Key”這種加密技術(shù)在當(dāng)今被廣泛采用,如美國(guó)政府所采用的DES加密標(biāo)準(zhǔn)就是一種典型的“對(duì)稱(chēng)式”加密法,它的Session Key長(zhǎng)度為56bits。
非對(duì)稱(chēng)式加密技術(shù)
非對(duì)稱(chēng)式加密就是加密和解密所使用的不是同一個(gè)密鑰,通常有兩個(gè)密鑰,稱(chēng)為“公鑰”和“私鑰”,它們兩個(gè)必需配對(duì)使用,否則不能打開(kāi)加密文件。這里的“公鑰”是指可以對(duì)外公布的,“私鑰”則不能,只能由持有人一個(gè)人知道。它的優(yōu)越性就在這里,因?yàn)閷?duì)稱(chēng)式的加密方法如果是在網(wǎng)絡(luò)上傳輸加密文件就很難不把密鑰告訴對(duì)方,不管用什么方法都有可能被別竊聽(tīng)到。而非對(duì)稱(chēng)式的加密方法有兩個(gè)密鑰,且其中的“公鑰”是可以公開(kāi)的,也就不怕別人知道,收件人解密時(shí)只要用自己的私鑰即可以,這樣就很好地避免了密鑰的傳輸安全性問(wèn)題。
我們國(guó)家出于信息安全考慮,研發(fā)了國(guó)密算法。國(guó)密算法,就是國(guó)家密碼局認(rèn)定的國(guó)產(chǎn)密碼算法,即商用密碼。
國(guó)密算法是國(guó)家密碼局制定標(biāo)準(zhǔn)的一系列算法。其中包括了對(duì)稱(chēng)加密算法,橢圓曲線非對(duì)稱(chēng)加密算法,雜湊算法。具體包括SM1,SM2,SM3等。
有興趣的小伙伴可以去國(guó)家密碼管理局(https://www.oscca.gov.cn/sca/index.shtml)了解下,網(wǎng)上也有很多現(xiàn)成的資源,今天我們就不講了。
常用的算法
Base64
嚴(yán)格意義上講,base64只能算一種編碼格式,并不能算加密,因?yàn)槿魏稳四玫?code style="overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 100, 65);">base64都可以解碼,但是base64在日常開(kāi)發(fā)中經(jīng)常被用到,比如參數(shù)編碼之后傳輸、圖片編碼后傳輸
/**
* base64加密
*
* @param sourcesStr
* @return
*/
public String encodeBase64(String sourcesStr) {
Base64.Encoder encoder = Base64.getEncoder();
String encodeToString = encoder.encodeToString(sourcesStr.getBytes());
return encodeToString;
}
/**
* base64解碼
*
* @param encodeBase64Str
* @return
*/
public String decodeBase64(String encodeBase64Str) {
Base64.Decoder decoder = Base64.getDecoder();
byte[] decode = decoder.decode(encodeBase64Str);
return new String(decode);
}
MD5
md5是一種不可被解密的加密方式,網(wǎng)上提供解密的網(wǎng)站,基本上都是先生成密碼庫(kù)(密文和明文的kv),當(dāng)你輸入密文后,檢索對(duì)應(yīng)的明文,如果存在就可以被解密,否則是沒(méi)法解密的,這種方式就是暴力破解,當(dāng)然如果某一天進(jìn)入了量子時(shí)代,那秒破md5也是可以的,但以目前的計(jì)算機(jī)算力,暴力破解一個(gè)超長(zhǎng)的md5密文,那是相當(dāng)難的
/**
* md5加密
* @param sourcesStr
* @return
*/
public String md5Encode(String sourcesStr) {
byte[] secretBytes = null;
try {
secretBytes = MessageDigest.getInstance("md5").digest(
sourcesStr.getBytes());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("沒(méi)有這個(gè)md5算法!");
}
String md5code = new BigInteger(1, secretBytes).toString(16);
for (int i = 0; i < 32 - md5code.length(); i++) {
md5code = "0" + md5code;
}
return md5code;
}
RSA
非對(duì)稱(chēng)加密算法的代表,RSA是被研究得最廣泛的公鑰算法,從提出到現(xiàn)在已近三十年,經(jīng)歷了各種攻擊的考驗(yàn),逐漸為人們接受,普遍認(rèn)為是目前最優(yōu)秀的公鑰方案之一。1983年麻省理工學(xué)院在美國(guó)為RSA算法申請(qǐng)了專(zhuān)利。
RSA允許你選擇公鑰的大小。512位的密鑰被視為不安全的;768位的密鑰不用擔(dān)心受到除了國(guó)家安全管理(NSA)外的其他事物的危害;1024位的密鑰幾乎是安全的。RSA在一些主要產(chǎn)品內(nèi)部都有嵌入,像 Windows、網(wǎng)景 Navigator、 Quicken和 Lotus Notes
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
import org.apache.commons.codec.binary.Hex;
public class RSATest {
// 數(shù)字簽名,密鑰算法
private static final String RSA_KEY_ALGORITHM = "RSA";
// 數(shù)字簽名簽名/驗(yàn)證算法
private static final String SIGNATURE_ALGORITHM = "SHA1WithRSA";
// RSA密鑰長(zhǎng)度
private static final int KEY_SIZE = 1024;
public static final String PUBLIC_KEY = "publicKey";
public static final String PRIVATE_KEY = "privateKey";
/**
* 初始化RSA密鑰對(duì)
* @return RSA密鑰對(duì)
* @throws Exception 拋出異常
*/
public static Map<String, String> initKey() throws Exception {
KeyPairGenerator keygen = KeyPairGenerator
.getInstance(RSA_KEY_ALGORITHM);
SecureRandom secrand = new SecureRandom();
secrand.setSeed("hahaha".getBytes());// 初始化隨機(jī)產(chǎn)生器
keygen.initialize(KEY_SIZE, secrand); // 初始化密鑰生成器
KeyPair keys = keygen.genKeyPair();
String pub_key = new String(Hex.encodeHex(keys.getPublic().getEncoded()));
String pri_key = new String(Hex.encodeHex(keys.getPrivate().getEncoded()));
Map<String, String> keyMap = new HashMap<String, String>();
keyMap.put(PUBLIC_KEY, pub_key);
keyMap.put(PRIVATE_KEY, pri_key);
System.out.println("公鑰:" + pub_key);
System.out.println("私鑰:" + pri_key);
return keyMap;
}
/**
* 得到公鑰
* @param keyMap RSA密鑰對(duì)
* @return 公鑰
* @throws Exception 拋出異常
*/
public static String getPublicKey(Map<String, String> keyMap) throws Exception{
return keyMap.get(PUBLIC_KEY);
}
/**
* 得到私鑰
* @param keyMap RSA密鑰對(duì)
* @return 私鑰
* @throws Exception 拋出異常
*/
public static String getPrivateKey(Map<String, String> keyMap) throws Exception{
return keyMap.get(PRIVATE_KEY);
}
/**
* 數(shù)字簽名
* @param data 待簽名數(shù)據(jù)
* @param pri_key 私鑰
* @return 簽名
* @throws Exception 拋出異常
*/
public static String sign(byte[] data, String pri_key) throws Exception {
// 取得私鑰
byte[] pri_key_bytes = Hex.decodeHex(pri_key);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(pri_key_bytes);
KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
// 生成私鑰
PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec);
// 實(shí)例化Signature
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
// 初始化Signature
signature.initSign(priKey);
// 更新
signature.update(data);
return new String(Hex.encodeHex(signature.sign()));
}
/**
* RSA校驗(yàn)數(shù)字簽名
* @param data 數(shù)據(jù)
* @param sign 簽名
* @param pub_key 公鑰
* @return 校驗(yàn)結(jié)果,成功為true,失敗為false
* @throws Exception 拋出異常
*/
public static boolean verify(byte[] data, byte[] sign, String pub_key) throws Exception {
// 轉(zhuǎn)換公鑰材料
// 實(shí)例化密鑰工廠
byte[] pub_key_bytes = Hex.decodeHex(pub_key.toCharArray());
KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
// 初始化公鑰
// 密鑰材料轉(zhuǎn)換
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(pub_key_bytes);
// 產(chǎn)生公鑰
PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
// 實(shí)例化Signature
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
// 初始化Signature
signature.initVerify(pubKey);
// 更新
signature.update(data);
// 驗(yàn)證
return signature.verify(sign);
}
/**
* 公鑰加密
* @param data 待加密數(shù)據(jù)
* @param pub_key 公鑰
* @return 密文
* @throws Exception 拋出異常
*/
private static byte[] encryptByPubKey(byte[] data, byte[] pub_key) throws Exception {
// 取得公鑰
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(pub_key);
KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
// 對(duì)數(shù)據(jù)加密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
/**
* 公鑰加密
* @param data 待加密數(shù)據(jù)
* @param pub_key 公鑰
* @return 密文
* @throws Exception 拋出異常
*/
public static String encryptByPubKey(String data, String pub_key) throws Exception {
// 私匙加密
byte[] pub_key_bytes = Hex.decodeHex(pub_key.toCharArray());
byte[] enSign = encryptByPubKey(data.getBytes(), pub_key_bytes);
return new String(Hex.encodeHex(enSign));
}
/**
* 私鑰加密
* @param data 待加密數(shù)據(jù)
* @param pri_key 私鑰
* @return 密文
* @throws Exception 拋出異常
*/
private static byte[] encryptByPriKey(byte[] data, byte[] pri_key) throws Exception {
// 取得私鑰
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(pri_key);
KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
// 對(duì)數(shù)據(jù)加密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
/**
* 私鑰加密
* @param data 待加密數(shù)據(jù)
* @param pri_key 私鑰
* @return 密文
* @throws Exception 拋出異常
*/
public static String encryptByPriKey(String data, String pri_key) throws Exception {
// 私匙加密
byte[] pri_key_bytes = Hex.decodeHex(pri_key.toCharArray());
byte[] enSign = encryptByPriKey(data.getBytes(), pri_key_bytes);
return new String(Hex.encodeHex(enSign));
}
/**
* 公鑰解密
* @param data 待解密數(shù)據(jù)
* @param pub_key 公鑰
* @return 明文
* @throws Exception 拋出異常
*/
private static byte[] decryptByPubKey(byte[] data, byte[] pub_key) throws Exception {
// 取得公鑰
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(pub_key);
KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
// 對(duì)數(shù)據(jù)解密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
/**
* 公鑰解密
* @param data 待解密數(shù)據(jù)
* @param pub_key 公鑰
* @return 明文
* @throws Exception 拋出異常
*/
public static String decryptByPubKey(String data, String pub_key) throws Exception {
// 公匙解密
byte[] pub_key_bytes = Hex.decodeHex(pub_key.toCharArray());
byte[] design = decryptByPubKey(Hex.decodeHex(data.toCharArray()), pub_key_bytes);
return new String(design);
}
/**
* 私鑰解密
* @param data 待解密數(shù)據(jù)
* @param pri_key 私鑰
* @return 明文
* @throws Exception 拋出異常
*/
private static byte[] decryptByPriKey(byte[] data, byte[] pri_key) throws Exception {
// 取得私鑰
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(pri_key);
KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
// 對(duì)數(shù)據(jù)解密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
/**
* 私鑰解密
* @param data 待解密數(shù)據(jù)
* @param pri_key 私鑰
* @return 明文
* @throws Exception 拋出異常
*/
public static String decryptByPriKey(String data, String pri_key) throws Exception {
// 私匙解密
byte[] pri_key_bytes = Hex.decodeHex(pri_key.toCharArray());
byte[] design = decryptByPriKey(Hex.decodeHex(data.toCharArray()), pri_key_bytes);
return new String(design);
}
/**
* @param args
*/
@SuppressWarnings("static-access")
public static void main(String[] args) throws Exception {
RSATest das = new RSATest();
String datastr = "天街小雨潤(rùn)如酥,草色遙看近卻無(wú)。最是一年春好處,絕勝煙柳滿(mǎn)皇都。";
System.out.println("待加密數(shù)據(jù):\n" + datastr);
//獲取密鑰對(duì)
Map<String, String> keyMap = new HashMap<String, String>();
keyMap = das.initKey();
String pub_key = (String) keyMap.get(PUBLIC_KEY);
String pri_key = (String) keyMap.get(PRIVATE_KEY);
// 公匙加密
String pubKeyStr = RSATest.encryptByPubKey(datastr, pub_key);
System.out.println("公匙加密結(jié)果:\n" + pubKeyStr);
// 私匙解密
String priKeyStr = RSATest.decryptByPriKey(pubKeyStr, pri_key);
System.out.println("私匙解密結(jié)果:\n" + priKeyStr);
//換行
System.out.println();
// 數(shù)字簽名
String str1 = "123";
String str2 = "123er";
System.out.println("正確的簽名:" + str1 + "\n錯(cuò)誤的簽名:" + str2);
String sign = RSATest.sign(str1.getBytes(), pri_key);
System.out.println("數(shù)字簽名:\n" + sign);
System.out.println("簽名長(zhǎng)度:" + sign.length());
boolean vflag1 = das.verify(str1.getBytes(), Hex.decodeHex(sign.toCharArray()), pub_key);
System.out.println("數(shù)字簽名驗(yàn)證結(jié)果1:\n" + vflag1);
boolean vflag2 = das.verify(str2.getBytes(), Hex.decodeHex(sign.toCharArray()), pub_key);
System.out.println("數(shù)字簽名驗(yàn)證結(jié)果2:\n" + vflag2);
}
}
另外兩種rsa加密算法——MD5withRSA和SHA1withRSA
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import client.util.HEX2Byte;
public class DigitalSignatureMain {
public static void main(String[] args) throws Exception {
String content = "study hard and make progress everyday";
System.out.println("content :" + content);
KeyPair keyPair = getKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
String md5Sign = getMd5Sign(content, privateKey);
System.out.println("sign with md5 and rsa :" + md5Sign);
boolean md5Verifty = verifyWhenMd5Sign(content, md5Sign, publicKey);
System.out.println("verify sign with md5 and rsa :" + md5Verifty);
String sha1Sign = getSha1Sign(content, privateKey);
System.out.println("簽名長(zhǎng)度:" + sha1Sign.length());
System.out.println("sign with sha1 and rsa :" + sha1Sign);
boolean sha1Verifty = verifyWhenSha1Sign(content, sha1Sign, publicKey);
System.out.println("verify sign with sha1 and rsa :" + sha1Verifty);
}
// 生成密鑰對(duì)
private static KeyPair getKeyPair() throws Exception {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(512); // 可以理解為:加密后的密文長(zhǎng)度,實(shí)際原文要小些 越大 加密解密越慢
KeyPair keyPair = keyGen.generateKeyPair();
return keyPair;
}
// 用md5生成內(nèi)容摘要,再用RSA的私鑰加密,進(jìn)而生成數(shù)字簽名
public static String getMd5Sign(String content, PrivateKey privateKey)
throws Exception {
byte[] contentBytes = content.getBytes("utf-8");
Signature signature = Signature.getInstance("MD5withRSA");
signature.initSign(privateKey);
signature.update(contentBytes);
byte[] signs = signature.sign();
return HEX2Byte.bytesToHex(signs);
}
// 對(duì)用md5和RSA私鑰生成的數(shù)字簽名進(jìn)行驗(yàn)證
public static boolean verifyWhenMd5Sign(String content, String sign,
PublicKey publicKey) throws Exception {
byte[] contentBytes = content.getBytes("utf-8");
Signature signature = Signature.getInstance("MD5withRSA");
signature.initVerify(publicKey);
signature.update(contentBytes);
return signature.verify(HEX2Byte.hexToByteArray(sign));
}
// 用sha1生成內(nèi)容摘要,再用RSA的私鑰加密,進(jìn)而生成數(shù)字簽名
public static String getSha1Sign(String content, PrivateKey privateKey)
throws Exception {
byte[] contentBytes = content.getBytes("utf-8");
Signature signature = Signature.getInstance("SHA1withRSA");
signature.initSign(privateKey);
signature.update(contentBytes);
byte[] signs = signature.sign();
return HEX2Byte.bytesToHex(signs);
}
// 對(duì)用md5和RSA私鑰生成的數(shù)字簽名進(jìn)行驗(yàn)證
public static boolean verifyWhenSha1Sign(String content, String sign,
PublicKey publicKey) throws Exception {
byte[] contentBytes = content.getBytes("utf-8");
Signature signature = Signature.getInstance("SHA1withRSA");
signature.initVerify(publicKey);
signature.update(contentBytes);
return signature.verify(HEX2Byte.hexToByteArray(sign));
}
}
工具類(lèi)
package client.util;
public class HEX2Byte {
/**
* hex字符串轉(zhuǎn)byte數(shù)組
*
* @param inHex
* 待轉(zhuǎn)換的Hex字符串
* @return 轉(zhuǎn)換后的byte數(shù)組結(jié)果
*/
public static byte[] hexToByteArray(String inHex) {
int hexlen = inHex.length();
byte[] result;
if (hexlen % 2 == 1) {
// 奇數(shù)
hexlen++;
result = new byte[(hexlen / 2)];
inHex = "0" + inHex;
} else {
// 偶數(shù)
result = new byte[(hexlen / 2)];
}
int j = 0;
for (int i = 0; i < hexlen; i += 2) {
result[j] = hexToByte(inHex.substring(i, i + 2));
j++;
}
return result;
}
/**
* Hex字符串轉(zhuǎn)byte
*
* @param inHex
* 待轉(zhuǎn)換的Hex字符串
* @return 轉(zhuǎn)換后的byte
*/
public static byte hexToByte(String inHex) {
return (byte) Integer.parseInt(inHex, 16);
}
/**
* 字節(jié)數(shù)組轉(zhuǎn)16進(jìn)制
*
* @param bytes
* 需要轉(zhuǎn)換的byte數(shù)組
* @return 轉(zhuǎn)換后的Hex字符串
*/
public static String bytesToHex(byte[] bytes) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xFF);
if (hex.length() < 2) {
sb.append(0);
}
sb.append(hex);
}
return sb.toString();
}
}
總結(jié)
加密算法這塊其實(shí)沒(méi)什么好總結(jié)的,畢竟算法大佬已經(jīng)幫你實(shí)現(xiàn)好了,你要做的就是把它改造成你需要的樣子。個(gè)人覺(jué)得,現(xiàn)在做軟件開(kāi)發(fā),讓完全勝任自己的工作,有一個(gè)能力特別重要,也特別基礎(chǔ),就是改造輪子的能力,畢竟現(xiàn)在開(kāi)發(fā)程序不可能任何需求都從零開(kāi)始,那太影響效率了,而且你造的輪子性能也不見(jiàn)得好。這也就是說(shuō),你要有看透輪子本質(zhì)的火眼金睛,要知道輪子解決了什么問(wèn)題,是否滿(mǎn)足你的需求,但這還是要回到造輪子上,只是功夫要花在平時(shí)學(xué)習(xí)的時(shí)候,這是一個(gè)需要長(zhǎng)期投入的事,當(dāng)然對(duì)你的成長(zhǎng)也是特別有助益的。好了,今天就到這里吧!
項(xiàng)目路徑:
https://github.com/Syske/example-everyday
本項(xiàng)目會(huì)每日更新,讓我們一起學(xué)習(xí),一起進(jìn)步,遇見(jiàn)更好的自己,加油呀
- END -