密码学小记

消息摘要算法

MD5

MD5(Message Digest Algorithm 5)中文名为消息摘要算法第五版,是计算机安全领域广泛使用的一种散列函数,用以提供消息的完整性保护

  • 输入任意长度信息,输出长度固定:
  • 散列后的密文不可逆
  • 散列后的结果唯一(但并不绝对可能产生哈希碰撞)
  • 哈希碰撞:原始数据与其MD5散列结果并非一一对应,存在多个原始数据的MD5结果相同的可能性
  • 一般用于校验数据完整性、签名sign

由于密文不可逆,所以服务端也无法解密,想要验证,就需要跟前端一样的方式去重新签名一遍,签名算法一般会把源数据和签名后的值一起提交到服务端,要保证在签名时候的数据和提交上去的源数据一致

try {
    MessageDigest md5 = MessageDigest.getInstance("MD5");
    md5.update("test".getBytes(StandardCharsets.UTF_8));
    md5.digest();
} catch (NoSuchAlgorithmException e) {
    e.printStackTrace();
}

SHA

MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
sha1.update("xiaojianbang".getBytes());
sha1.digest();
  1. 加密后的字节数组可以编码成Hex、Base64
  2. 没有任何输入,也能计算hash值

MAC

MAC(Message Authentication Codes),是一种消息摘要算法,也叫消息认证码算法。
这种算法的核心是基于秘钥的散列函数。
可以理解为,MAC算法,是MD5算法和SHA算法的升级版,是在这两种算法的基础上,又加入了秘钥的概念,更加安全。
所以,有时候又叫MAC算法为HMAC算法(keyed-Hash Message Authentication Codes),即含有秘钥的散列算法。

常见的MAC算法有以下两大类:

  • MD系列:HmacMD2、HmacMD4、HmacMD5
  • SHA系列:HmacSHA1、HmacSHA224、HmacSHA256、HmacSHA384、HmacSHA512

大致使用方法:
发送端根据message和一个mac算法,生成mac秘钥
将mac秘钥和message同时发送
接收端收到message,用同样的mac算法,得到mac秘钥,
判断自己生成mac秘钥和接收到的mac秘钥是否一致,从而判断message是否一致

MAC算法与MD和SHA的区别是多了一个密钥,密钥可以随机给
加密后的字节数组可以编码成Hex、Base64
没有任何输入,也能计算hash值

SecretKeySpec secretKeySpec = new SecretKeySpec("a12345678".getBytes(),"HmacSHA1");
Mac mac = Mac.getInstance(secretKeySpec.getAlgorithm());
mac.init(secretKeySpec);
mac.update("xiaojianbang".getBytes());
mac.doFinal();

对称加密算法

  1. 加密/解密的过程可逆的算法,叫做加密算法
  2. 加密/解密使用相同的密钥,叫做对称加密算法
  3. 对称加密算法的密钥可以随机给,但是有位数要求
  4. 对称加密算法的输入数据没有长度要求,加密速度快
  5. 各算法的密钥长度
    • RC4 密钥长度1-256字节
    • DES 密钥长度8字节
    • 3DES/DESede/TripleDES 密钥长度24字节
    • AES 密钥长度16、24、32字节
    • 根据密钥长度不同AES又分为AES-128、AES-192、AES-256
  6. 对称加密分类
    • 序列加密/流加密: 以字节流的方式,依次加密(解密)明文(密文)中的每一个字节
      • RC4
    • 分组加密: 将明文消息分组(每组有多个字节),逐组进行加密
      • DES、3DES、AES

DES

DES ECB 模式加解密代码实现

public static void main(String[] args) {
    String encryptStr = encryptDES("test");
    System.out.println(encryptStr);
    String decryptStr = decryptDES(ByteString.decodeHex(encryptStr).toByteArray());
    System.out.println(decryptStr);
}

public static String encryptDES(String plainText) {
    try {
        //生成 DESKey
        SecretKeySpec desKey = new SecretKeySpec("12345678".getBytes(), "DES");
        //可以同时指定工作模式和填充方式,不指定会使用默认的工作模式和填充方式
        Cipher des = Cipher.getInstance("DES/ECB/PKCS5Padding");
        //Cipher.ENCRYPT_MODE 1 为加密 , 2 为解密
        des.init(Cipher.ENCRYPT_MODE, desKey);
        byte[] bytes = des.doFinal(plainText.getBytes());
        return ByteString.of(bytes).hex();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

public static String decryptDES(byte[] cipherTextBytes) {
    try {
        //这种方式也可以获取 key
        DESKeySpec desKeySpec = new DESKeySpec("12345678".getBytes());
        SecretKeyFactory key = SecretKeyFactory.getInstance("DES");
        SecretKey secretKey = key.generateSecret(desKeySpec);
        //SecretKeySpec desKey = new SecretKeySpec("12345678".getBytes(), "DES");
        Cipher des = Cipher.getInstance("DES/ECB/PKCS5Padding");
        des.init(Cipher.DECRYPT_MODE, secretKey);
        byte[] bytes = des.doFinal(cipherTextBytes);
        return new String(bytes);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

DES CBC 模式加解密代码实现

public static String encryptDES(String plainText) {
    try {
        SecretKeySpec desKey = new SecretKeySpec("12345678".getBytes(), "DES");
        Cipher des = Cipher.getInstance("DES/CBC/PKCS5Padding");
        IvParameterSpec ivParameterSpec = new IvParameterSpec("12345678".getBytes());
        des.init(Cipher.ENCRYPT_MODE, desKey, ivParameterSpec);
        byte[] bytes = des.doFinal(plainText.getBytes());
        return ByteString.of(bytes).hex();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

public static String decryptDES(byte[] cipherTextBytes) {
    try {
        SecretKeySpec desKey = new SecretKeySpec("12345678".getBytes(), "DES");
        Cipher des = Cipher.getInstance("DES/CBC/PKCS5Padding");
        IvParameterSpec ivParameterSpec = new IvParameterSpec("12345678".getBytes());
        des.init(Cipher.DECRYPT_MODE, desKey, ivParameterSpec);
        byte[] bytes = des.doFinal(cipherTextBytes);
        return new String(bytes);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

DES 加密流程

DES是分组加密,每8个字节分为一个组,如果末尾组不够8个字节则通过指定的填充方式进行填充

  • ECB 模式每个组单独加密之间互不干扰
  • 在CBC模式中多一个IV向量配置,加密结果是受IV向量影响的,下一个分组会受上一个分组影响 ,使用第一组明文和IV向量进行异或,异或的结果进行加密,加密后得到的密文在和下一组明文进行异或,异或的结果进行加密且得到密文和下一组明文参与运算,以此类推

CBC 和 ECB 的区别

  1. 对称加密算法里,使用NOPadding,加密的明文必须等于分组长度倍数,否则报错
  2. 如果使用PKCS5Padding,会对加密的明文填充1字节-1个分组的长度
  3. 没有指明加密模式和填充方式,表示使用默认的DES/ECB/PKCS5Padding
  4. 加密后的字节数组可以编码成Hex、Base64
  5. DES算法明文按64位进行分组加密,可以调用 cipher.getBlockSize()来获取
  6. 要复现一个对称加密算法,需要得到明文keyivmodepadding
  7. 明文、key、iv需要注意解析方式,而且不一定是字符串形式
  8. 如果加密模式是ECB,则不需要加iv,加了的话会报错
  9. 如果明文中有两个分组的内容相同,ECB会得到完全一样的密文,CBC不会
  10. 加密算法的结果通常与明文等长或者更长,如果变短了,那可能是gzip、protobuf

DESede

DESede是针对DES密钥长度偏短和迭代次数偏少等问题做了相应改进,提高了安全强度。但是导致的问题算法处理速度较慢,密钥计算时间较长,加密效率不高等。

DESede算法将密钥长度增加至112位或168位,抗穷举攻击能力显著增强,但核心仍是DES算法,虽然通过增加迭代次数提高了安全性,但同时也造成了处理速度较慢,密钥计算时间加长,加密效率不高等问题。

DESede 代码实现

public static String encryptDESede(String plainText) {
    try {
        SecretKeySpec DESedeKey = new SecretKeySpec("123456781234567812345678".getBytes(), "DESede");
        Cipher DESede = Cipher.getInstance("DESede/CBC/PKCS5Padding");
        IvParameterSpec ivParameterSpec = new IvParameterSpec("12345678".getBytes());
        DESede.init(Cipher.ENCRYPT_MODE, DESedeKey, ivParameterSpec);
        byte[] bytes = DESede.doFinal(plainText.getBytes());
        return ByteString.of(bytes).hex();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

public static String decryptDESede(byte[] cipherTextBytes) {
    try {
        //这种方式也可以获取 key
        DESedeKeySpec DESedeKeySpec = new DESedeKeySpec("123456781234567812345678".getBytes());
        SecretKeyFactory key = SecretKeyFactory.getInstance("DESede");
        SecretKey secretKey = key.generateSecret(DESedeKeySpec);
        //SecretKeySpec DESedeKey = new SecretKeySpec("123456781234567812345678".getBytes(), "DESede");
        Cipher DESede = Cipher.getInstance("DESede/CBC/PKCS5Padding");
        IvParameterSpec ivParameterSpec = new IvParameterSpec("12345678".getBytes());
        DESede.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
        byte[] bytes = DESede.doFinal(cipherTextBytes);
        return new String(bytes);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

AES


根据密钥长度不同,分为AES128、AES192、AES256

AES 代码实现

public static void main(String[] args) {
    String encryptStr = encryptAES("TEST012346789abcTEST012346789abc");
    System.out.println(encryptStr);
    String decryptStr = decryptAES(ByteString.decodeHex(encryptStr).toByteArray());
    System.out.println(decryptStr);
}

public static String encryptAES(String plainText) {
    try {
        SecretKeySpec desKey = new SecretKeySpec("0123456789abcdef".getBytes(), "AES");
        IvParameterSpec ivParameterSpec = new IvParameterSpec("0123456789abcdef".getBytes());
        Cipher des = Cipher.getInstance("AES/CBC/PKCS5Padding");
        des.init(Cipher.ENCRYPT_MODE, desKey, ivParameterSpec);
        byte[] bytes = des.doFinal(plainText.getBytes());
        return ByteString.of(bytes).hex();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

public static String decryptAES(byte[] cipherTextBytes) {
    try {
        SecretKeySpec desKey = new SecretKeySpec("0123456789abcdef".getBytes(), "AES");
        IvParameterSpec ivParameterSpec = new IvParameterSpec("0123456789abcdef".getBytes());
        Cipher des = Cipher.getInstance("AES/CBC/PKCS5Padding");
        des.init(Cipher.DECRYPT_MODE, desKey, ivParameterSpec);
        byte[] bytes = des.doFinal(cipherTextBytes);
        return new String(bytes);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

非对称加密算法

典型算法:RSA

  1. 需要生成一个密钥对,包含公钥和私钥,密钥不是随便写的,密钥对生成 ✈️ http://web.chacuo.net/netrsakeypair
  2. 公钥加密的数据,私钥才能解密,私钥加密的数据,公钥才能解密
  3. 一般公钥是公开的,私钥保密,私钥包含公钥,从公钥无法推导出私钥
  4. 加密处理安全,但是性能极差,单次加密长度有限制
  5. RSA算法既可用于加密解密,也可用于数据签名

RSA_Base64

私钥的格式

  • pkcs1 格式通常开头是 -----BEGIN RSA PRIVATE KEY-----
  • pkcs8 格式通常开头是 -----BEGIN PRIVATE KEY-----
  • Java中的私钥必须是 pkcs8 格式,如果是 pkcs1 的密钥可以转成 pkcs8

RSA模式和填充细节

  1. None模式与ECB模式是一致的
  2. NoPadding
    明文最多字节数为密钥字节数
    密文与密钥等长
    填充字节0, 加密后的密文不变
  3. pkcs1padding
    明文最大字节数为密钥字节数-11
    密文与密钥等长
    每一次的填充不一样,使得加密后的密文会变
public class RSA_Base64 {

    private static String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDXnAxEDlWrgw1nqam/VXvO969L\n" +
            "eY2+kBHwhONck9/fQKfWnmi0tqaK9/WQQnU+HrDffKPvTw8o4GDjqk0GBME1JdMv\n" +
            "9/7h8f/dAZciDelwl/c1B0hlIWLHX64sh8VYsy8y3FLst4HhH22+0MRamdKAfZcq\n" +
            "6cAxVL5YOYWtnvKABwIDAQAB";
    private static String privateKey = "MIICeQIBADANBgkqhkiG9w0BAQEFAASCAmMwggJfAgEAAoGBANecDEQOVauDDWep\n" +
            "qb9Ve873r0t5jb6QEfCE41yT399Ap9aeaLS2por39ZBCdT4esN98o+9PDyjgYOOq\n" +
            "TQYEwTUl0y/3/uHx/90BlyIN6XCX9zUHSGUhYsdfriyHxVizLzLcUuy3geEfbb7Q\n" +
            "xFqZ0oB9lyrpwDFUvlg5ha2e8oAHAgMBAAECgYEAqiWFMHe3d5/2BiZHxKwrBgtE\n" +
            "FGWaTBXZclsMKVSwyLd3O9DKhEHXb7d53Bv19c22escbf5B+QB3BmCgenG9IH7r4\n" +
            "nHvxh7v/nCdeB4CBq4inXEjOeAemGJ/2yHjdT7voR4DAdxBdy4F0cF2w5dwvQQ6F\n" +
            "owYEfnrjGyYfBzRhlpECQQD+izHiCaX3WTKklsfNckj/EGq6fk2nQwgbak5GzzLi\n" +
            "KQuiIwKq5Lsqq4mS1GpL/aYCTRdE0QGiLvPHPGIjAwXNAkEA2NfUe+ByO4mwGbEC\n" +
            "y0FjcLGfmeK6cBCtn/Xa8u6Z4E8v8aa0D7LHsYn6TrslIlYAKVhOGDryhQDLfODV\n" +
            "xyaJIwJBALJZFy94cSgZAGngF8i0Xb8RYqae3ovmZKTI3GWywcVC2xrUiwbwUs/3\n" +
            "9uHBIWVzKMEOANK/2vQnD7m2blB3yrUCQQCpGgdWY34x9/ogzu3C3EdUBvDT7QA/\n" +
            "jIIaEHyinnfZeWeGJ96br2wTg+pzo3YeBLszgu3D75RZLHmD9UJBgEfJAkEAuu6B\n" +
            "DyXJs6vt/WV+CMJVZHWah6BkTfkR2Z1qpVjfjXUJ4iKQzP0p8mRzYUDgDju6Ca2W\n" +
            "fDZKIV9H7hLT+mvvRg==";


    public static void main(String[] args) {
        String encrptyStr = encrptyByPublicKey("test");
        System.out.println(encrptyStr);
        String decrptyStr = decrptyByPublicKey(ByteString.decodeHex(encrptyStr).toByteArray());
        System.out.println(decrptyStr);
    }

    /**
     * 公钥加密
     * @param plainText
     * @return
     */
    public static String encrptyByPublicKey(String plainText){
        try {
            PublicKey publicKey = generatePublicKey(RSA_Base64.publicKey);
            Cipher instance = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            instance.init(Cipher.ENCRYPT_MODE,publicKey);
            byte[] bytes = instance.doFinal(plainText.getBytes());
            return ByteString.of(bytes).hex();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 私钥解密
     * @param plainText
     * @return
     */
    public static String decrptyByPublicKey(byte[] cipherTextBytes){
        try {
            PrivateKey privateKey = generatePrivateKey(RSA_Base64.privateKey);
            Cipher instance = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            instance.init(Cipher.DECRYPT_MODE,privateKey);
            byte[] bytes = instance.doFinal(cipherTextBytes);
            return new String(bytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 解析 RSA 公钥
     *
     * @param publicKeyBase64
     * @return
     */
    public static PublicKey generatePublicKey(String publicKeyBase64) {
        try {
            byte[] publicKeyBase64Bytes = ByteString.decodeBase64(publicKeyBase64).toByteArray();
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyBase64Bytes);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            return keyFactory.generatePublic(x509EncodedKeySpec);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 解析 RSA 私钥
     *
     * @param privateKeyBase64
     * @return
     */
    public static PrivateKey generatePrivateKey(String privateKeyBase64) {
        try {
            byte[] publicKeyBase64Bytes = ByteString.decodeBase64(privateKeyBase64).toByteArray();
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(publicKeyBase64Bytes);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            return keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

}

RSA_Hex

RSA密钥的转换

如果碰到需要使用16进制证书的场景,这种证书没办法直接使用,我们需要先从里面提取出 modulus、publicExponent、privateExponent ,我们这里生成一对HEX格式的公私钥进行演示:

公钥:

30819f300d06092a864886f70d010101050003818d0030818902818100adb90f91710f58272feec7fd13c3692b2283e7f031daa11c2ec0200cd1b6b21ee8835db9d79e8c13dbe0c95a24ea08232a5ad4859c4aff9e17e67dbf29bf82358e77f21e6099d02b7749f1b40ef62982e1bc4694fff38a4f231d03672d72cff9ead2da7b35bc8c37de3bc4894d20f305f7a77d36d550d0c00416cf98e98db2090203010001

私钥

30820276020100300d06092a864886f70d0101010500048202603082025c02010002818100adb90f91710f58272feec7fd13c3692b2283e7f031daa11c2ec0200cd1b6b21ee8835db9d79e8c13dbe0c95a24ea08232a5ad4859c4aff9e17e67dbf29bf82358e77f21e6099d02b7749f1b40ef62982e1bc4694fff38a4f231d03672d72cff9ead2da7b35bc8c37de3bc4894d20f305f7a77d36d550d0c00416cf98e98db20902030100010281807251c0ebede1b219ff91fbab0ff15ce8d4cac6bbaaa2fbb8670f6947e64d7a839a70b021d6c16fdeb5a803f8bd2800f8db2b70850827d674bbb7a2f2444fd127c5ad47f9335c55a99ae32d0b9c7884a6ca3acd02aef0cabdbc804fb1ea170f709681672de9d9699efbd8efde890d14f63f41f854a94b246ac1f3b5fbbcd110dd024100d403444e3fe23bb8df5d3054aca8f19780dce0bd04e2dea3beb2c1862ccd7b04ce5ce01877da35f08aa144a07ffc32533edc8c0eca68e115573de455291612b3024100d1c41619557f17da2db6cc442eb00eacb20689c0dac2422c2bdff2c971a920b30b5773896581f6c75221c20e8f78a3bc11d63478ec17f73acdd017b5df2cd6530240202996b5202fdbcb81e70b2bed3d7bd8f5ed8c1260a962090926e900c7cf2c38606dea790bc588a543028ffca12dcb1ca1cea7589f1026052cc4f0dc926d0ccd024052ccb0e06d240eb93b64357b1066c4541cad820093192cdf9cadf87ad597a9e5a2ab715aa1abcc4a5f3c1e0a7b4d666c1d2a4d6a11df5fe2614d2b9ec9aa3c2b024100aa9118f83af365dcfb3ded873689475b945edc0cd7b4880d0c46733fa9a2877d99db597e7d0d88a9a2bf9a5868fae2ba6f5f9ea0057dc2c4159fc5a7287fa1e3
通过openssl提取
openssl rsa -pubin -in public.pem -text  
openssl rsa -in private.pem -text      
  1. 把HEX格式私钥解码重新编码为PEM格式
  2. 从PEM格式密钥中提取 modulus、publicExponent、privateExponent
手动提取

modulus

adb90f91710f58272feec7fd13c3692b2283e7f031daa11c2ec0200cd1b6b21ee8835db9d79e8c13dbe0c95a24ea08232a5ad4859c4aff9e17e67dbf29bf82358e77f21e6099d02b7749f1b40ef62982e1bc4694fff38a4f231d03672d72cff9ead2da7b35bc8c37de3bc4894d20f305f7a77d36d550d0c00416cf98e98db209

publicExponent

010001

如果只需要加密那么有 modulus 和 publicExponent 就足够了,但是如果要解密,我们就还需要从私钥中提取 privateExponent ,接着我们来提取 privateExponent

privateExponent

7251c0ebede1b219ff91fbab0ff15ce8d4cac6bbaaa2fbb8670f6947e64d7a839a70b021d6c16fdeb5a803f8bd2800f8db2b70850827d674bbb7a2f2444fd127c5ad47f9335c55a99ae32d0b9c7884a6ca3acd02aef0cabdbc804fb1ea170f709681672de9d9699efbd8efde890d14f63f41f854a94b246ac1f3b5fbbcd110dd

RSA密钥的解析

RSA_Hex除了初始化方式不通,加解密的方式与RSA_Base64一致

 public static PublicKey generatePublicKey() {
     try {
         BigInteger n = new BigInteger(modulus, 16);
         BigInteger e = new BigInteger(publicExponent, 16);
         RSAPublicKeySpec rsaPublicKeySpec = new RSAPublicKeySpec(n, e);
         KeyFactory keyFactory = KeyFactory.getInstance("RSA");
         return keyFactory.generatePublic(rsaPublicKeySpec);
     } catch (Exception e) {
         e.printStackTrace();
     }
     return null;
 }
 
 public static PrivateKey generatePrivateKey() {
     try {
         BigInteger n = new BigInteger(modulus, 16);
         BigInteger d = new BigInteger(privateExponent, 16);
         RSAPrivateKeySpec rsaPrivateKeySpec = new RSAPrivateKeySpec(n, d);
         KeyFactory keyFactory = KeyFactory.getInstance("RSA");
         return keyFactory.generatePrivate(rsaPrivateKeySpec);
     } catch (Exception e) {
         e.printStackTrace();
     }
     return null;
 }

多种加密算法的常见结合套路

  1. 随机生成 AES 密钥 AESKey
  2. AESKey 密钥用于 AES 加密数据,得到数据密文 cipherText
  3. 使用 RSA 对 AESKey 加密,得到密钥密文cipherKey
  4. 提交密钥密文 cipherKey 和 数据密文 cipherText 给服务器
  5. 服务器拿到 cipherKey 和 cipherText 使用 RSA 解密 cipherKey
  6. 然后通过解密的 cipherKey 去解密 cipherText
public class Test {
    
    public static String generateAesKey() {
        StringBuffer stringBuffer = new StringBuffer();
        Random random = new Random();
        for (int i = 0; i < 16; i++) {
            int random_i = random.nextInt(100);
            String temp = "0" + Integer.toHexString(random_i);
            temp = temp.substring(temp.length() - 2);
            stringBuffer.append(temp);
        }
        return stringBuffer.toString();
    }

    public static void main(String[] args) {
        // 随机生成 aesKey
        String aesKey = generateAesKey();
        // 使用随机生成的 aesKey 加密需要加密的数据
        String cipherText = AES.encryptAES("test", aesKey);
        // 使用 rsa 加密 随机生成的 aesKey 并发送 cipherText , cipherKey 到服务器
        String cipherKey = RSA_Base64.encrptyByPublicKey(aesKey);

        // 模拟服务器接收并解密
        // 通过 RSA 解密 cipherKey
        String plainKey = RSA_Base64.decrptyByPublicKey(ByteString.decodeHex(cipherKey).toByteArray());
        // 通过解密的 cipherKey 解密 cipherText
        String plainText = AES.decryptAES(ByteString.decodeBase64(cipherText).toByteArray(), plainKey);
        System.out.println("plainKey:" + plainKey + " plainText:" + plainText);
    }
}
public class RSA_Base64 {

    private static String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDXnAxEDlWrgw1nqam/VXvO969L\n" +
            "eY2+kBHwhONck9/fQKfWnmi0tqaK9/WQQnU+HrDffKPvTw8o4GDjqk0GBME1JdMv\n" +
            "9/7h8f/dAZciDelwl/c1B0hlIWLHX64sh8VYsy8y3FLst4HhH22+0MRamdKAfZcq\n" +
            "6cAxVL5YOYWtnvKABwIDAQAB";
    private static String privateKey = "MIICeQIBADANBgkqhkiG9w0BAQEFAASCAmMwggJfAgEAAoGBANecDEQOVauDDWep\n" +
            "qb9Ve873r0t5jb6QEfCE41yT399Ap9aeaLS2por39ZBCdT4esN98o+9PDyjgYOOq\n" +
            "TQYEwTUl0y/3/uHx/90BlyIN6XCX9zUHSGUhYsdfriyHxVizLzLcUuy3geEfbb7Q\n" +
            "xFqZ0oB9lyrpwDFUvlg5ha2e8oAHAgMBAAECgYEAqiWFMHe3d5/2BiZHxKwrBgtE\n" +
            "FGWaTBXZclsMKVSwyLd3O9DKhEHXb7d53Bv19c22escbf5B+QB3BmCgenG9IH7r4\n" +
            "nHvxh7v/nCdeB4CBq4inXEjOeAemGJ/2yHjdT7voR4DAdxBdy4F0cF2w5dwvQQ6F\n" +
            "owYEfnrjGyYfBzRhlpECQQD+izHiCaX3WTKklsfNckj/EGq6fk2nQwgbak5GzzLi\n" +
            "KQuiIwKq5Lsqq4mS1GpL/aYCTRdE0QGiLvPHPGIjAwXNAkEA2NfUe+ByO4mwGbEC\n" +
            "y0FjcLGfmeK6cBCtn/Xa8u6Z4E8v8aa0D7LHsYn6TrslIlYAKVhOGDryhQDLfODV\n" +
            "xyaJIwJBALJZFy94cSgZAGngF8i0Xb8RYqae3ovmZKTI3GWywcVC2xrUiwbwUs/3\n" +
            "9uHBIWVzKMEOANK/2vQnD7m2blB3yrUCQQCpGgdWY34x9/ogzu3C3EdUBvDT7QA/\n" +
            "jIIaEHyinnfZeWeGJ96br2wTg+pzo3YeBLszgu3D75RZLHmD9UJBgEfJAkEAuu6B\n" +
            "DyXJs6vt/WV+CMJVZHWah6BkTfkR2Z1qpVjfjXUJ4iKQzP0p8mRzYUDgDju6Ca2W\n" +
            "fDZKIV9H7hLT+mvvRg==";


    /**
     * 公钥加密
     * @param plainText
     * @return
     */
    public static String encrptyByPublicKey(String plainText){
        try {
            PublicKey publicKey = generatePublicKey(RSA_Base64.publicKey);
            Cipher instance = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            instance.init(Cipher.ENCRYPT_MODE,publicKey);
            byte[] bytes = instance.doFinal(plainText.getBytes());
            return ByteString.of(bytes).hex();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 私钥解密
     * @param cipherTextBytes
     * @return
     */
    public static String decrptyByPublicKey(byte[] cipherTextBytes){
        try {
            PrivateKey privateKey = generatePrivateKey(RSA_Base64.privateKey);
            Cipher instance = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            instance.init(Cipher.DECRYPT_MODE,privateKey);
            byte[] bytes = instance.doFinal(cipherTextBytes);
            return new String(bytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 解析 RSA 公钥
     *
     * @param publicKeyBase64
     * @return
     */
    public static PublicKey generatePublicKey(String publicKeyBase64) {
        try {
            byte[] publicKeyBase64Bytes = ByteString.decodeBase64(publicKeyBase64).toByteArray();
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyBase64Bytes);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            return keyFactory.generatePublic(x509EncodedKeySpec);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 解析 RSA 私钥
     *
     * @param privateKeyBase64
     * @return
     */
    public static PrivateKey generatePrivateKey(String privateKeyBase64) {
        try {
            byte[] publicKeyBase64Bytes = ByteString.decodeBase64(privateKeyBase64).toByteArray();
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(publicKeyBase64Bytes);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            return keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

}
public class AES {
    public static String encryptAES(String plainText, String aesKey) {
        try {
            SecretKeySpec desKey = new SecretKeySpec(ByteString.decodeHex(aesKey).toByteArray(), "AES");
            IvParameterSpec ivParameterSpec = new IvParameterSpec("0123456789abcdef".getBytes());
            Cipher des = Cipher.getInstance("AES/CBC/PKCS5Padding");
            des.init(Cipher.ENCRYPT_MODE, desKey, ivParameterSpec);
            byte[] bytes = des.doFinal(plainText.getBytes());
            return ByteString.of(bytes).base64();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static String decryptAES(byte[] cipherTextBytes, String aesKey) {
        try {
            SecretKeySpec desKey = new SecretKeySpec(ByteString.decodeHex(aesKey).toByteArray(), "AES");
            IvParameterSpec ivParameterSpec = new IvParameterSpec("0123456789abcdef".getBytes());
            Cipher des = Cipher.getInstance("AES/CBC/PKCS5Padding");
            des.init(Cipher.DECRYPT_MODE, desKey, ivParameterSpec);
            byte[] bytes = des.doFinal(cipherTextBytes);
            return new String(bytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

数字签名算法

数字签名算法用于消息的验证防止伪造,例如a给b发消息 ,中间可能会出现c拦截消息篡改后发送给b,b收到消息以为是a发过来的消息并不知道消息进行了篡改

签名流程

  1. 发送者对消息计算摘要值。
  2. 发送者用私钥对摘要值进行签名得到签名值。
  3. 发送者将原始消息和签名值一同发给接收者
public static String getSignature(String data) throws Exception {
    PrivateKey privateKey = RSA_Base64.generatePrivateKey(SignatureTest.privateKey);
    Signature sig = Signature.getInstance("SHA256withRSA");
    sig.initSign(privateKey);
    sig.update(data.getBytes());
    byte[] sign = sig.sign();
    return ByteString.of(sign).base64();
}

验证流程

  1. 接收者接收到消息后,拆分出消息和消息签名值A。
  2. 接收者使用公钥对消息进行运算得到摘要值B。
  3. 接收者对摘要值B和签名值A进行比较,如果相同表示签名验证成功,否则就是验证失败。
public static Boolean verifySignature(String data, String sign) throws Exception {
    PublicKey publicKey = RSA_Base64.generatePublicKey(SignatureTest.publicKey);
    Signature sig = Signature.getInstance("SHA256withRSA");
    sig.initVerify(publicKey);
    sig.update(data.getBytes());
    boolean verify = sig.verify(ByteString.decodeBase64(sign).toByteArray());
    return verify;
}

CryptoJS

消息摘要算法

CryptoJS中消息摘要算法的使用

CryptoJS.MD5(message);
CryptoJS.HmacMD5(message, key);
CryptoJS.SHA1(message);
CryptoJS.HmacSHA1(message, key);
CryptoJS.SHA256(message);
CryptoJS.HmacSHA256(message, key);
CryptoJS.SHA512(message);
CryptoJS.HmacSHA512(message, key);
CryptoJS.SHA3('test', {outputLength: 256})

消息摘要算法的其他调用形式

// SHA256
var hasher = CryptoJS.algo.SHA256.create();
hasher.reset();
hasher.update('message');
var hash = hasher.finalize();
// HmacSHA256
var hmacHasher = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, key);
hmacHasher.reset();
hmacHasher.update('message');
var hmac = hmacHasher.finalize();

字符串解析

string 转 wordArray

CryptoJS.enc.Utf8.parse(utf8String);
CryptoJS.enc.Hex.parse(hexString);
CryptoJS.enc.Base64.parse(base64String);

wordArray 转 string

wordArray + '';
wordArray.toString();
wordArray.toString(CryptoJS.enc.Utf8);
wordArray.toString(CryptoJS.enc.Hex);
wordArray.toString(CryptoJS.enc.Base64);
CryptoJS.enc.Utf8.stringify(wordArray);
CryptoJS.enc.Hex.stringify(wordArray);
CryptoJS.enc.Base64.stringify(wordArray);

如果函数传入的参数是string类型的数据,将使用默认的 Utf8.parse 来解析

对称加密算法

CryptoJS中对称加密算法的使用

var ciphertext = CryptoJS.DES.encrypt(message, key, cfg);
var plaintext  = CryptoJS.DES.decrypt(ciphertext, key, cfg);
var ciphertext = CryptoJS.TripleDES.encrypt(message, key, cfg);
var plaintext  = CryptoJS.TripleDES.decrypt(ciphertext, key, cfg);
var ciphertext = CryptoJS.AES.encrypt(message, key, cfg);
var plaintext  = CryptoJS.AES.decrypt(ciphertext, key, cfg);
var ciphertext = CryptoJS.RC4.encrypt(message, key, cfg);
var plaintext  = CryptoJS.RC4.decrypt(ciphertext, key, cfg);
var cfg = {
    iv: iv,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7,
    format: CryptoJS.format.Hex
};
  • cfg中没有传 mode 和 padding ,默认使用CBC的加密模式,Pkcs7的填充方式
  • 加密结果是wordArray对象,调用toString默认转Base64编码的密文,转hex可以使用
      var hexString = wordArray.ciphertext.toString();
    
  • CryptoJS中提供的加密模式
    • CBCECBCFBOFBCTRGladmanCTR
  • CryptoJS中提供的填充方式
    • NoPaddingZeroPaddingPkcs7(Pkcs5)Iso10126Iso97971AnsiX923

JS数字签名库

var signData = "test";
var privateKeyBase64 = "-----BEGIN PRIVATE KEY-----MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAPFFAfEv/zFnURo2\n" +
    "ZAEZmekyIJjuBHOiDqcON8ElpzK0SJUmclG6rVX8P4kppcPB62wKdJRrPrksIUdT\n" +
    "T05IRh57mgKIjSqXbUQDfTpz3WhP99Ck+eBAZkctS0M5R0lWUAqeJwK+ZHbg2rI0\n" +
    "oW5jwvRycHXNxrHvdF/4K1/+XEA7AgMBAAECgYEAsGkDrYWps0bW7zKb1o4Qkojb\n" +
    "etZ2HNJ+ojlsHObaJOHbPGs7JXU4bmmdTz5LfSIacAoJCciMuTqCLrPEhfmkghPq\n" +
    "U2MjyjfqYdXALoP7l/vt6QmjY/g1IAsaZN9nFhyjJ2WzgOx1f7gZj4NBSvTdSj7H\n" +
    "m5E24zkm+p7Qw1z6/mkCQQD7WSXAXcv2v3Vo6qi1FUlkzQgCQLFYqXNSOSPpno3y\n" +
    "oohUFIkMj0bYGbVE1LzV30Rb6Z8e8yQAByw6l8RuGb2PAkEA9bwb2euyOe6CcqpE\n" +
    "PNFc+7UlOJAy5epVFKHbu0aNivVpU0hsphqjIGXJGHYTspyEOLqtzILqKPZr6pru\n" +
    "WvJUlQJBAJoImQUZtlyCGs7wN/G5mN/ocscGpGikd+Lk16hdHbqbdpaoexCyYYUf\n" +
    "xCHpicw75mW5d2V9Ngu6WZWS2rNqnOsCQCoMK//X8sEy7KNOOyrk8DIpxtqs4eix\n" +
    "dil3oK+k3OdgIsubYuvxNuR+RjCnU6uGWKGUX9TUudiUgda89/gb6xkCQFm8gD6n\n" +
    "AyN+PPPKRq2M84+cAbnvjdIAY3OFHfkaoWCtEj5DR0UDuVv7jN7+re2D7id/GkAe\n" +
    "FAmhvYQwwLnifrw=-----END PRIVATE KEY-----";

function doSign() {
    var signature = KEYUTIL.getKey(privateKeyBase64);
    var hSig = signature.signString(signData, "sha256");
    return hex2b64(hSig);
}

console.log(doSign());
赞赏