一.哈希算法
哈希算法(hash)又称摘要算法(Digest),它的作用是:对任意一组输入数据进行计算,得到一个长度固定的输出摘要。哈希算法的目的:为了验证原始数据是否被篡改。
哈希算法最重要的特点:
1.相同的输入一定得到相同的输出。
2.不同的输入大概率得不到相同的输出。
以字符串为例,调用hashcode()方法,输出的是固定的4字节int整数。
"hello".hashCode(); // 0x5e918d2
"hello, java".hashCode(); // 0x7a9d88e8
"hello, bob".hashCode(); // 0xa0dbae2f
2.常见的哈希算法
我们首先以MD5为例,看看如何输入计算哈希。
创建MEssageDigest实例,调用getInstance()方法,并传入MD5类型,继而调用update()方法更新原始数据,创建字节数组调用digest()方法,通过Stringbuilder()将遍历出来的字节拼接起来。具体代码如下:
public static void main(String[] args) {
String str = "wbjxxmynhmyzgq";
try {//根据当前算法,获取加密工具
MessageDigest mag = MessageDigest.getInstance("MD5");
//更新原始数据
mag.update(str.getBytes());
//加密后的字节数组,转换成字符串
byte[] byteArray = mag.digest();
StringBuilder result = new StringBuilder();
for(byte bute : byteArray) {
result.append(String.format("%02x", bute));
}
System.out.println(result);
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
SHA-1:,为了避免彩虹表攻击,我们可以对数据进行“加盐”处理,也就是说对口令添加随机数,SHA-1与MD5一致,只将算法类型换成SHA-1,具体代码如下:
public static void main(String[] args) {
String password = "wbjxxmy";
//加盐(安全)
String salt = UUID.randomUUID().toString().substring(0,5);
System.out.println(salt);
try {//获取SHA-1算法的工具对象
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.update(password.getBytes());
digest.update(salt.getBytes());
byte[] resultByteArray = digest.digest();
System.out.println(Arrays.toString(resultByteArray));
StringBuilder sb = new StringBuilder();
for(byte s :resultByteArray) {
sb.append(String.format("%02x", s));
}
System.out.println("SHA-1加密:"+sb);
System.out.println("----------------------");
Hmac算法:Hmac算法就是一种基于密钥的消息认证算法,他的全称是Hsh-basedMessage Authrntication Code,是一种更安全的消息摘要算法。
Hmac算法总是和某种哈希算法配合起来用的,例如,我们使用MD5,算法,对应的就是Hmac MD5算法,它就相当于“加盐”的MD5,因此hmac可以看作是一个更安全的MD5,首先创建KeyGenerator实例调用getInstance()方法,并传入参数。通过KeyGenerator对象调用generateKey()方法生成密钥,通过密钥对象调用getEncoded()方法获取密钥字节数组,
最后遍历字节数组,并通过Stringbuilder进行拼接。创建Mac实例对象,调用getInstance()方法,传入参数,调用init()初始化密钥,调用update()更新原始内容,调用doFinal()进行加密,并存入字节数组中。最后遍历数组,通过Stringbuilder进行拼接。具体代码实现如下:
String password = "xlfjkcs";
try {//生成密钥
//密钥生成器KeyGenerator
KeyGenerator keyGen = KeyGenerator.getInstance("HmacMD5");
//生成密钥Key
SecretKey key = keyGen.generateKey();
//获取密钥Key的字节数组(64)
byte[] keyByteArray = key.getEncoded();
System.out.println("密钥长度:"+keyByteArray.length+"字节");
StringBuilder sb = new StringBuilder();
for(byte b :keyByteArray) {
sb.append(String.format("%02x", b));
}
System.out.println("密钥内容:"+sb);
System.out.println("密钥内容长度:"+sb.length());
//2.使用密钥,进行加密
//获取算法对象
Mac mac = Mac.getInstance("HmacMD5");
//初始化密钥
mac.init(key);
//更新原始内容
mac.update(password.getBytes());
//加密
byte[] resultByteArray = mac.doFinal();
System.out.println("加密结果:"+resultByteArray.length+"字节");
StringBuilder resultstr = new StringBuilder();
for(byte s :resultByteArray) {
resultstr.append(String.format("%02x", s));
}
System.out.println("加密结果:"+resultstr);
System.out.println("加密结果长度:"+resultstr.length());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
RipeMD160算法:第三方开源库提供的算法,具体代码实现如下:
public class Main {
public static void main(String[] args) throws Exception {
// 注册BouncyCastle提供的通知类对象BouncyCastleProvider
Security.addProvider(new BouncyCastleProvider());
// 获取RipeMD160算法的"消息摘要对象"(加密对象)
MessageDigest md = MessageDigest.getInstance("RipeMD160");
// 更新原始数据
md.update("HelloWorld".getBytes());
// 获取消息摘要(加密)
byte[] result = md.digest();
// 消息摘要的字节长度和内容
System.out.println(result.length); // 160位=20字节
System.out.println(Arrays.toString(result));
// 16进制内容字符串
String hex = new BigInteger(1,result).toString(16);
System.out.println(hex.length()); // 20字节=40个字符
System.out.println(hex);
}
}
对称式加密:对称式加密算法就是用一个密码进行加密和解密,在软件开发中,常见的对称加密算法有如下:
当然,现在使用最广泛的就是AES加密,其中比较常见的工作模式是ECB和CBC。
以ECB模式为例:我们将它的加密和解密封装在两个方法中,方便直接调用。
public class Main {
public static void main(String[] args) throws Exception {
// 原文:
String message = "Hello, world!";
System.out.println("Message(原始信息): " + message);
// 128位密钥 = 16 bytes Key:
byte[] key = "1234567890abcdef".getBytes();
// 加密:
byte[] data = message.getBytes();
byte[] encrypted = encrypt(key, data);
System.out.println("Encrypted(加密内容): " +
Base64.getEncoder().encodeToString(encrypted));
// 解密:
byte[] decrypted = decrypt(key, encrypted);
System.out.println("Decrypted(解密内容): " + new String(decrypted));
}
// 加密:
public static byte[] encrypt(byte[] key, byte[] input) throws GeneralSecurityException {
// 创建密码对象,需要传入算法/工作模式/填充模式
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
// 根据key的字节内容,"恢复"秘钥对象
SecretKey keySpec = new SecretKeySpec(key, "AES");
// 初始化秘钥:设置加密模式ENCRYPT_MODE
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
// 根据原始内容(字节),进行加密
return cipher.doFinal(input);
}
// 解密:
public static byte[] decrypt(byte[] key, byte[] input) throws GeneralSecurityException {
// 创建密码对象,需要传入算法/工作模式/填充模式
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
// 根据key的字节内容,"恢复"秘钥对象
SecretKey keySpec = new SecretKeySpec(key, "AES");
// 初始化秘钥:设置解密模式DECRYPT_MODE
cipher.init(Cipher.DECRYPT_MODE, keySpec);
// 根据原始内容(字节),进行解密
return cipher.doFinal(input);
}
}
ECB模式下,需要一个固定长度的密钥,固定的明文会生成固定的密文,这种一对一的加密方式会导致安全性降低,更好的方式是通过CBC模式,他需要一个随机数作为IV参数,这样对于同一份明文,每次生成的密文都不同:
public class Main {
public static void main(String[] args) throws Exception {
// 原文:
String message = "Hello, world!";
System.out.println("Message(原始信息): " + message);
// 256位密钥 = 32 bytes Key:
byte[] key = "1234567890abcdef1234567890abcdef".getBytes();
// 加密:
byte[] data = message.getBytes();
byte[] encrypted = encrypt(key, data);
System.out.println("Encrypted(加密内容): " +
Base64.getEncoder().encodeToString(encrypted));
// 解密:
byte[] decrypted = decrypt(key, encrypted);
System.out.println("Decrypted(解密内容): " + new String(decrypted));
}
// 加密:
public static byte[] encrypt(byte[] key, byte[] input) throws GeneralSecurityException {
// 设置算法/工作模式CBC/填充
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
// 恢复秘钥对象
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
// CBC模式需要生成一个16 bytes的initialization vector:
SecureRandom sr = SecureRandom.getInstanceStrong();
byte[] iv = sr.generateSeed(16); // 生成16个字节的随机数
System.out.println(Arrays.toString(iv));
IvParameterSpec ivps = new IvParameterSpec(iv); // 随机数封装成IvParameterSpec参数对象
// 初始化秘钥:操作模式、秘钥、IV参数
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivps);
// 加密
byte[] data = cipher.doFinal(input);
// IV不需要保密,把IV和密文一起返回:
return join(iv, data);
}
// 解密:
public static byte[] decrypt(byte[] key, byte[] input) throws GeneralSecurityException {
// 把input分割成IV和密文:
byte[] iv = new byte[16];
byte[] data = new byte[input.length - 16];
System.arraycopy(input, 0, iv, 0, 16); // IV
System.arraycopy(input, 16, data, 0, data.length); //密文
System.out.println(Arrays.toString(iv));
// 解密:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); // 密码对象
SecretKeySpec keySpec = new SecretKeySpec(key, "AES"); // 恢复秘钥
IvParameterSpec ivps = new IvParameterSpec(iv); // 恢复IV
// 初始化秘钥:操作模式、秘钥、IV参数
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivps);
// 解密操作
return cipher.doFinal(data);
}
// 合并数组
public static byte[] join(byte[] bs1, byte[] bs2) {
byte[] r = new byte[bs1.length + bs2.length];
System.arraycopy(bs1, 0, r, 0, bs1.length);
System.arraycopy(bs2, 0, r, bs1.length, bs2.length);
return r;
}
}
非对称式加密:加密和解密使用的不是相同的密钥,只有同一个公钥—私钥才能正常的加解密。
例如:小卢要加密一个文件给小吴,他应该首先向小吴索取他的公钥,然后用索取的公钥加密,然后把加密的文件发给小吴,此文件只有小吴的私钥解开,因为小吴的私钥在自己手里,所以,除了了小吴,没有人能解开此文件。
典型算法RSA,代码实现如下:
// RSA
public class Main {
public static void main(String[] args) throws Exception {
// 明文:
byte[] plain = "Hello, encrypt use RSA".getBytes("UTF-8");
// 创建公钥/私钥对:
Human alice = new Human("Alice");
// 用Alice的公钥加密:
// 获取Alice的公钥,并输出
byte[] pk = alice.getPublicKey();
System.out.println(String.format("public key(公钥): %x", new BigInteger(1, pk)));
// 使用公钥加密
byte[] encrypted = alice.encrypt(plain);
System.out.println(String.format("encrypted(加密): %x", new BigInteger(1, encrypted)));
// 用Alice的私钥解密:
// 获取Alice的私钥,并输出
byte[] sk = alice.getPrivateKey();
System.out.println(String.format("private key(私钥): %x", new BigInteger(1, sk)));
// 使用私钥解密
byte[] decrypted = alice.decrypt(encrypted);
System.out.println("decrypted(解密): " + new String(decrypted, "UTF-8"));
}
}
// 用户类
class Human {
// 姓名
String name;
// 私钥:
PrivateKey sk;
// 公钥:
PublicKey pk;
// 构造方法
public Human(String name) throws GeneralSecurityException {
// 初始化姓名
this.name = name;
// 生成公钥/私钥对:
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA");
kpGen.initialize(1024);
KeyPair kp = kpGen.generateKeyPair();
this.sk = kp.getPrivate();
this.pk = kp.getPublic();
}
// 把私钥导出为字节
public byte[] getPrivateKey() {
return this.sk.getEncoded();
}
// 把公钥导出为字节
public byte[] getPublicKey() {
return this.pk.getEncoded();
}
// 用公钥加密:
public byte[] encrypt(byte[] message) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, this.pk); // 使用公钥进行初始化
return cipher.doFinal(message);
}
// 用私钥解密:
public byte[] decrypt(byte[] input) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, this.sk); // 使用私钥进行初始化
return cipher.doFinal(input);
}
}
对称加密与非对称加密对比:
对称加密在加密或解密中,使用的都是相同的密钥,而非对称加密则是使用密钥对(公钥---私钥)使用公钥进行加密,解密时则必须使用该公钥对应的私钥进行解密。
请各位大佬指正。