对称加密算法

对称加密算法,加密和解密所用密钥一样。常用的对称加密算法有 DES,DESede(又称3DES),AES,RC2,RC4。其中DESede和RC2,RC4都是DES的替代算法。实际AES用的最多。所以这里暂时只总结AES的用法。 

import org.junit.Test;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class Demo {

    @Test
    public void test() throws Exception {
        String value = "rawchen";
        System.out.println("待加密值:" + value);
        // 加密算法
        String algorithm = "AES";
        // 转换模式
        String transformation = "AES";
        // --- 生成秘钥 ---
        // 实例化秘钥生成器
        KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm);
        // 初始化秘钥长度
        keyGenerator.init(256);
        // 生成秘钥
        SecretKey secretKey = keyGenerator.generateKey();
        // 生成秘钥材料
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), algorithm);
        System.out.println("AES秘钥:" + Base64.getEncoder().encodeToString(secretKey.getEncoded()));

        // 实例化密码对象
        Cipher cipher = Cipher.getInstance(transformation);
        // 设置模式(ENCRYPT_MODE:加密模式;DECRYPT_MODE:解密模式)和指定秘钥
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
        // 加密
        byte[] encrypt = cipher.doFinal(value.getBytes());
        System.out.println("AES加密结果:" + Base64.getEncoder().encodeToString(encrypt));
        // 解密
        // 设置为解密模式
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
        byte[] decrypt = cipher.doFinal(encrypt);
        System.out.println("AES解密结果:" + new String(decrypt));
    }
}

结果

待加密值:rawchen
AES秘钥:kSiEi1NwlB+dRdzNtI+ACzcZVk5jX5C6jASByxTHBqg=
AES加密结果:l6+wvdlNmasW2PpVjAhWhw==
AES解密结果:rawchen

生成密钥时也可以生成加密密钥,例如

/**
     * 生成加密秘钥
     *
     * @return
     */
    private static SecretKeySpec getSecretKey(String key) {
        //返回生成指定算法密钥生成器的 KeyGenerator 对象
        KeyGenerator kg = null;

        try {
            kg = KeyGenerator.getInstance(KEY_ALGORITHM);

            //兼容linux和window的内核
            SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
            secureRandom.setSeed(key.getBytes(Charset.forName("UTF-8")));

            //AES 要求密钥长度为 128
            kg.init(128, secureRandom);

            //生成一个密钥
            SecretKey secretKey = kg.generateKey();

            // 转换为AES专用密钥
            return new SecretKeySpec(secretKey.getEncoded(), KEY_ALGORITHM);
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(AESUtil.class.getName()).log(Level.SEVERE, null, ex);
        }

        return null;
    }

转换模式为AES/CBC/PKCS5Padding时,需要指定初始化向量,cipher.init()方法多了一个初始化向量参数,如

import org.junit.Test;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class Demo {

    @Test
    public void test() throws Exception {
        String value = "rawchen";
        System.out.println("待加密值:" + value);
        // 加密算法
        String algorithm = "AES";
        // 转换模式
        String transformation = "AES/CBC/PKCS5Padding";
        // --- 生成秘钥 ---
        // 实例化秘钥生成器
        KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm);
        // 初始化秘钥长度
        keyGenerator.init(256);
        // 生成秘钥
        SecretKey secretKey = keyGenerator.generateKey();
        // 生成秘钥材料
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), algorithm);
        System.out.println("AES秘钥:" + Base64.getEncoder().encodeToString(secretKey.getEncoded()));

        // 初始化向量,123456789abcdefg初始化向量秘钥,16字节
        IvParameterSpec iv = new IvParameterSpec("123456789abcdefg".getBytes());
        // 实例化密码对象
        Cipher cipher = Cipher.getInstance(transformation);
        // 设置模式(ENCRYPT_MODE:加密模式;DECRYPT_MODE:解密模式)和指定秘钥
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, iv);
        // 加密
        byte[] encrypt = cipher.doFinal(value.getBytes());
        System.out.println("AES加密结果:" + Base64.getEncoder().encodeToString(encrypt));
        // 解密
        // 设置为解密模式
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, iv);
        byte[] decrypt = cipher.doFinal(encrypt);
        System.out.println("AES解密结果:" + new String(decrypt));
    }
}

输出

待加密值:rawchen
AES秘钥:+hAO/51KV9FAgMHMEPwc+RpUlpEfBjyzJ699LRYFLf8=
AES加密结果:/pnOn0QjFc8QYjZTuuAKTg==
AES解密结果:rawchen
手动指定密钥

import org.junit.Test;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class Demo {

    @Test
    public void test() throws Exception {
        String algorithm = "AES";
        String transformation = "AES/CTR/PKCS5Padding";
        String key = "xaBsoZIBs1Dz6veBfwfzpPwzrmMsu8mKqu4Lljk+zZo=";
        String encrypt = "VKOtjAOQJQ==";
        String ivKey = "123456789abcdefg";

        Cipher cipher = Cipher.getInstance(transformation);
        SecretKeySpec secretKeySpec = new SecretKeySpec(Base64.getDecoder().decode(key), algorithm);
        IvParameterSpec iv = new IvParameterSpec(ivKey.getBytes());
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, iv);
        byte[] decrypt = cipher.doFinal(Base64.getDecoder().decode(encrypt));
        System.out.println("AES解密结果:" + new String(decrypt));
    }
}
AES解密结果:rawchen

非对称加密算法

非对称加密算法有两个密钥,一个公钥,一个私钥。公钥加密,私钥解密。非对称加密算法也用来签名和验证签名。私钥用来签名,公钥用来验证签名。(比如微信的支付通知,签名使用微信平台私钥,验证使用平台公钥)常用的算法有RSA,ECC。RSA用的比较多。这里只总结RSA的用法

  • 从公钥和私钥字符串获取公私钥
  • 生成加解密密码器,加解密

从公钥和私钥字符串获取公钥

private static final String SIGN_TYPE_RSA = "RSA";
 private static final String SIGN_ALGORITHMS = "SHA1WithRSA";
 private static final int MAX_ENCRYPT_BLOCK_1024 = 117;
 private static final int MAX_DECRYPT_BLOCK_1024 = 128;
 private static final int MAX_ENCRYPT_BLOCK_2048 = 245;
 private static final int MAX_DECRYPT_BLOCK_2048 = 256;
 private static final int MAX_ENCRYPT_LIMIT = 300;
 private static final int MAX_DECRYPT_LIMIT = 1000;
private static PublicKey getPublicKeyFromX509(String algorithm, InputStream ins) throws Exception {
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        StringWriter writer = new StringWriter();
        StreamUtils.io(new InputStreamReader(ins), writer);
        byte[] encodedKey = writer.toString().getBytes();
        encodedKey = Base64.getDecoder().decode(encodedKey);
        return keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
    }

从公钥和私钥字符串获取私钥

public static PrivateKey getPrivateKeyFromPKCS8(String algorithm, InputStream ins) throws Exception {
        if (ins == null || algorithm == null) {
            return null;
        }
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        byte[] encodedKey = StreamUtils.readText(ins).getBytes();
        encodedKey = Base64.getDecoder().decode(encodedKey);
        return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));
    }

生成加解密密码器,加密

/**
     * 使用RSA公钥对业务数据加密
     *
     * @param content   业务数据json
     * @param publicKey RSA公钥
     * @return 加密后的字符串
     * @throws Exception e
     */
    public static String rsaEncrypt(String content, String publicKey) throws Exception {
        PublicKey pubKey = getPublicKeyFromX509(SIGN_TYPE_RSA, new ByteArrayInputStream(publicKey.getBytes()));
        Cipher cipher = Cipher.getInstance(SIGN_TYPE_RSA);
        cipher.init(Cipher.ENCRYPT_MODE, pubKey);
        byte[] data = content.getBytes(StandardCharsets.UTF_8);
        data = cipher.doFinal(data);
        byte[] encryptedData = Base64.getEncoder().encode(data);
        return new String(encryptedData, StandardCharsets.UTF_8);
    }

生成加解密密码器,加密

/**
     * RSA解密
     * @param content 密文
     * @param privateKey 私钥
     * @return 解密后的明文
     * @throws Exception e
     */
    public static String rsaDecrypt(String content, String privateKey) throws Exception {
        PrivateKey priKey = getPrivateKeyFromPKCS8(SIGN_TYPE_RSA, new ByteArrayInputStream(privateKey.getBytes()));
        Cipher cipher = Cipher.getInstance(SIGN_TYPE_RSA);
        cipher.init(Cipher.DECRYPT_MODE, priKey);
        byte[] encryptedData = Base64.getDecoder().decode(content.getBytes(StandardCharsets.UTF_8));
        byte[] decryptedData = cipher.doFinal(encryptedData)
        return new String(decryptedData, StandardCharsets.UTF_8);
    }

分段加解密
RSA加解密中必须考虑到的密钥长度、明文长度和密文长度问题。明文长度需要小于密钥长度,而密文长度则等于密钥长度。因此当加密内容长度大于密钥长度时,有效的RSA加解密就需要对内容进行分段。

/**
     * 使用RSA公钥对业务数据加密
     *
     * @param content   业务数据json
     * @param publicKey RSA公钥
     * @return 加密后的字符串
     * @throws Exception e
     */
    public static String rsaEncrypt(String content, String publicKey) throws Exception {
        PublicKey pubKey = getPublicKeyFromX509(SIGN_TYPE_RSA, new ByteArrayInputStream(publicKey.getBytes()));
        Cipher cipher = Cipher.getInstance(SIGN_TYPE_RSA);
        cipher.init(Cipher.ENCRYPT_MODE, pubKey);
        byte[] data = content.getBytes(StandardCharsets.UTF_8);
        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;

        int maxEncryptBlock = MAX_ENCRYPT_BLOCK_1024;

        if (publicKey.length() > MAX_ENCRYPT_LIMIT) {
            maxEncryptBlock = MAX_ENCRYPT_BLOCK_2048;
        }
        // 对数据分段加密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > maxEncryptBlock) {
                cache = cipher.doFinal(data, offSet, maxEncryptBlock);
            } else {
                cache = cipher.doFinal(data, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * maxEncryptBlock;
        }
        byte[] encryptedData = Base64.getEncoder().encode(out.toByteArray());
        out.close();
        return new String(encryptedData, StandardCharsets.UTF_8);
    }
/**
     * RSA解密
     * @param content 密文
     * @param privateKey 私钥
     * @return 解密后的明文
     * @throws Exception e
     */
    public static String rsaDecrypt(String content, String privateKey) throws Exception {
        PrivateKey priKey = getPrivateKeyFromPKCS8(SIGN_TYPE_RSA, new ByteArrayInputStream(privateKey.getBytes()));
        Cipher cipher = Cipher.getInstance(SIGN_TYPE_RSA);
        cipher.init(Cipher.DECRYPT_MODE, priKey);
        byte[] encryptedData = Base64.getDecoder().decode(content.getBytes(StandardCharsets.UTF_8));
        int inputLen = encryptedData.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        int maxDecryptBlock = MAX_DECRYPT_BLOCK_1024;
        if (privateKey.length() > MAX_DECRYPT_LIMIT) {
            maxDecryptBlock = MAX_DECRYPT_BLOCK_2048;
        }
        // 对数据分段解密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > maxDecryptBlock) {
                cache = cipher.doFinal(encryptedData, offSet, maxDecryptBlock);
            } else {
                cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * maxDecryptBlock;
        }
        byte[] decryptedData = out.toByteArray();
        out.close();

        return new String(decryptedData, StandardCharsets.UTF_8);
    }

常见的签名算法

MD5算法
MD5 用的是 哈希函数,它的典型应用是对一段信息产生 信息摘要,以 防止被篡改。严格来说,MD5 不是一种 加密算法 而是 摘要算法。无论是多长的输入,MD5 都会输出长度为 128bits 的一个串 (通常用 16 进制 表示为 32 个字符)。

public static final byte[] computeMD5(byte[] content) {
    try {
        MessageDigest md5 = MessageDigest.getInstance("MD5");
        return md5.digest(content);
    } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException(e);
    }
}

SHA1算法
SHA1 是和 MD5 一样流行的 消息摘要算法,然而 SHA1 比 MD5 的 安全性更强。对于长度小于 2 ^ 64 位的消息,SHA1 会产生一个 160 位的 消息摘要。基于 MD5、SHA1 的信息摘要特性以及 不可逆 (一般而言),可以被应用在检查 文件完整性 以及 数字签名 等场景。

public static byte[] computeSHA1(byte[] content) {
    try {
        MessageDigest sha1 = MessageDigest.getInstance("SHA1");
        return sha1.digest(content);
    } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException(e);
    }
}

HMAC算法
HMAC 是密钥相关的 哈希运算消息认证码(Hash-based Message Authentication Code),HMAC 运算利用 哈希算法 (MD5、SHA1 等),以 一个密钥 和 一个消息 为输入,生成一个 消息摘要 作为 输出。

HMAC 发送方 和 接收方 都有的 key 进行计算,而没有这把 key 的第三方,则是 无法计算 出正确的 散列值的,这样就可以 防止数据被篡改。

package net.pocrd.util;
import net.pocrd.annotation.NotThreadSafe;
import net.pocrd.define.ConstField;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Arrays;


@NotThreadSafe
public class HMacHelper {
    private static final Logger logger = LoggerFactory.getLogger(HMacHelper.class);
    private Mac mac;

    /**
     * MAC算法可选以下多种算法
     * HmacMD5/HmacSHA1/HmacSHA256/HmacSHA384/HmacSHA512
     */
    private static final String KEY_MAC = "HmacMD5";
    public HMacHelper(String key) {
        try {
            SecretKey secretKey = new SecretKeySpec(key.getBytes(ConstField.UTF8), KEY_MAC);
            mac = Mac.getInstance(secretKey.getAlgorithm());
            mac.init(secretKey);
        } catch (Exception e) {
            logger.error("create hmac helper failed.", e);
        }
    }
    public byte[] sign(byte[] content) {
        return mac.doFinal(content);
    }

    public boolean verify(byte[] signature, byte[] content) {
        try {
            byte[] result = mac.doFinal(content);
            return Arrays.equals(signature, result);
        } catch (Exception e) {
            logger.error("verify sig failed.", e);
        }
        return false;
    }
}

SHA256算法

SHA256是SHA2算法中的一种,如SHA2加密算法中有:SHA244、SHA256、SHA512等。SHA2属于SHA1的升级,SHA1是160位的哈希值,而SHA2是组合值,有不同的位数,其中最受欢迎的是256位(SHA256算法)。

SSL行业选择SHA作为数字签名的散列算法,从2011到2015,一直以SHA-1位主导算法。但随着互联网技术的提升,SHA-1的缺点越来越突显。从去年起,SHA-2成为了新的标准,所以现在签发的SSL证书,必须使用该算法签名。

public static byte[] getSHA256(String str) {
    MessageDigest messageDigest;
    String encodestr = "";
    try {
        messageDigest = MessageDigest.getInstance("SHA-256");
        messageDigest.update(str.getBytes("UTF-8"));
        return messageDigest.digest());
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }
}

SHA1withRSA算法数据签名

/**
     * SHA1withRSA算法数据加签
     *
     * @param content    要签名的字符串
     * @param privateKey 私钥
     * @return 签名串
     * @throws Exception e
     */
    public static String rsaSign(String content, String privateKey) throws Exception {
        PrivateKey priKey = getPrivateKeyFromPKCS8(SIGN_TYPE_RSA,
                new ByteArrayInputStream(privateKey.getBytes(StandardCharsets.UTF_8)));
        Signature signature = Signature.getInstance(SIGN_ALGORITHMS);
        signature.initSign(priKey);
        signature.update(content.getBytes(StandardCharsets.UTF_8));
        byte[] signed = signature.sign();
        return new String(Base64.getEncoder().encode(signed));
    }

SHA1withRSA算法数据签名验签

/**
     * SHA1withRSA算法数据验签
     * @param content 待验签字符串
     * @param sign 签文
     * @param publicKey 公钥
     * @return result
     * @throws Exception e
     */
    public static boolean rsaCheckSign(String content, String sign, String publicKey) throws Exception {
        PublicKey pubKey = getPublicKeyFromX509(SIGN_TYPE_RSA, new ByteArrayInputStream(publicKey.getBytes()));
        Signature signature = Signature.getInstance(SIGN_ALGORITHMS);
        signature.initVerify(pubKey);
        signature.update(content.getBytes(StandardCharsets.UTF_8));
        return signature.verify(Base64.getDecoder().decode(sign.getBytes()));
    }

概括来说,加密步骤如下:
1、生成或获取密钥
2、生成指定算法的加解密密码器
3、使用密钥对加解密密码器初始化
4、传入加解密模式和明文或密文参数执行加解密方法

生成密钥,不同的算法略有区别:
对称加密算法可通过 javax.crypto.KeyGenerator 密钥生成器生成如:

KeyGenerator keyGenerator = KeyGenerator.getInstance("RSA");
 keyGenerator.init(128);
 SecretKey secretKey = keyGenerator.generateKey();
 获取密钥的初始编码格式的字节数组
 byte[] key = secretKey.getEncoded();
 从字节数组还原为密钥
 SecretKeySpec secretKeySpec = new SecretKeySpec(key, "算法字符串");
 将密钥转为Base64编码的字符串
 String keyString = Base64.getEncoder().encodeToString(key)

还有一种生成密钥的API:密钥工厂,不过部分算法才支持,如des

由密钥的字节数组获取密钥
 DESKeySpec desKeySpec = new DESKeySpec(secretKey.getEncoded());
 SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("算法字符串");
 SecretKey desSecretKey = secretKeyFactory.generateSecret(desKeySpec);

非对称加密算法可通过java.security.KeyPairGenerator 密钥对生成器,生成公钥和私钥

KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("算法签名如RSA");
 // 初始化,秘钥长度512~16384位,64倍数
 keyPairGenerator.initialize(512);
 // 生成秘钥对
 KeyPair keyPair = keyPairGenerator.generateKeyPair();
 // 公钥
 PublicKey publicKey = keyPair.getPublic();
 System.out.println("RSA公钥: " + Base64.getEncoder().encodeToString(publicKey.getEncoded()));
 // 私钥
 PrivateKey privateKey = keyPair.getPrivate();
 System.out.println("RSA私钥: " + Base64.getEncoder().encodeToString(privateKey.getEncoded()));