原创:扣钉日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处。
简介
前面在密码学入门一文中讲解了各种常见的密码学概念、算法与运用场景,但没有介绍过代码,因此,为作补充,这一篇将会介绍使用Java语言如何实现使用这些算法,并介绍一下使用过程中可能遇到的坑。
Java加密体系JCA
Java抽象了一套密码算法框架JCA(Java Cryptography Architecture),在此框架中定义了一套接口与类,以规范Java平台密码算法的实现,而Sun,SunRsaSign,SunJCE这些则是一个个JCA的实现Provider,以实现具体的密码算法,这有点像List与ArrayList、LinkedList的关系一样,Java开发者只需要使用JCA即可,而不用管具体是怎么实现的。
JCA里定义了一系列类,如Cipher、MessageDigest、MAC、Signature等,分别用于实现加密、密码学哈希、认证码、数字签名等算法,一起来看看吧!
对称加密
对称加密算法,使用Cipher类即可,以广泛使用的AES为例,如下:
public byte[] encrypt(byte[] data, Key key) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = SecureRandoms.randBytes(cipher.getBlockSize());
//初始化密钥与加密参数iv
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
//加密
byte[] encryptBytes = cipher.doFinal(data);
//将iv与密文拼在一起
ByteArrayOutputStream baos = new ByteArrayOutputStream(iv.length + encryptBytes.length);
baos.write(iv);
baos.write(encryptBytes);
return baos.toByteArray();
} catch (Exception e) {
return ExceptionUtils.rethrow(e);
}
}
public byte[] decrypt(byte[] data, Key key) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
//获取密文前面的iv
IvParameterSpec ivSpec = new IvParameterSpec(data, 0, cipher.getBlockSize());
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
//解密iv后面的密文
return cipher.doFinal(data, cipher.getBlockSize(), data.length - cipher.getBlockSize());
} catch (Exception e) {
return ExceptionUtils.rethrow(e);
}
}
如上,对称加密主要使用Cipher,不管是AES还是DES,Cipher.getInstance()
传入不同的算法名称即可,这里的Key参数就是加密时使用的密钥,稍后会介绍它是怎么来的,暂时先忽略它。
另外,为了使得每次加密出来的密文不同,我使用了随机的iv向量,并将iv向量拼接在了密文前面。
注:如果某个算法名称,如上面的
AES/CBC/PKCS5Padding
,你不知道它在JCA中的标准名称是什么,可以到 https://docs.oracle.com/en/java/javase/11/docs/specs/security/standard-names.html 中查询即可。
非对称加密
非对称加密同样是使用Cipher类,只是传入的密钥对象不同,以RSA算法为例,如下:
public byte[] encryptByPublicKey(byte[] data, PublicKey publicKey){
try{
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(data);
}catch (Exception e) {
throw Errors.toRuntimeException(e);
}
}
public byte[] decryptByPrivateKey(byte[] data, PrivateKey privateKey){
try{
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(data);
}catch (Exception e) {
throw Errors.toRuntimeException(e);
}
}
一般来说应使用公钥加密,私钥解密,但其实反过来也是可以的,这里的PublicKey与PrivateKey也先忽略,后面会介绍它怎么来的。
密码学哈希
密码学哈希算法包括MD5、SHA1、SHA256等,在JCA中都使用MessageDigest类即可,如下:
public static String sha256(byte[] bytes) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.update(bytes);
return Hex.encodeHexString(digest.digest());
}
消息认证码
消息认证码使用Mac类实现,以常见的HMAC搭配SHA256为例,如下:
public byte[] digest(byte[] data, Key key) throws InvalidKeyException, NoSuchAlgorithmException{
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(key);
return mac.doFinal(data);
}
数字签名
数字签名使用Signature类实现,以RSA搭配SHA256为例,如下:
public byte[] sign(byte[] data, PrivateKey privateKey) {
try {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(data);
return signature.sign();
} catch (Exception e) {
return ExceptionUtils.rethrow(e);
}
}
public boolean verify(byte