Skip to content

weileifrank/CipherBaseOnJava

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Java密码学相关知识梳理

ASCII编码

  • ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。。
  • Logo
  • 实例代码:
public static void main(String[] args) {
        char a = 'A';
        int code = a;
        System.out.println(code);

        String s = "frank";
        char[] chars = s.toCharArray();
        for (char c : chars) {
            int num = c;
            System.out.println(num);
        }
}

凯撒加密

  • 恺撒密码(Caesar cipher)是一种相传尤利乌斯·恺撒曾使用过的密码。恺撒于公元前100年左右诞生于古罗马,是一位著名的军事统帅。

  • 恺撤密码是通过将明文中所使用的字母表按照一定的字数“平移”来进行加密的

  • Logo

  • 凯撒密码加解密公式

    • 加密

    • 解密

    • 凯撒密码中的加密三要素

      • 明文/密文
        • 明文: 图表上部分的数据
        • 密文: 图表下部分的数据
      • 秘钥
        • 按照上图秘钥为3
      • 算法
        • 加密: +3
        • 解密: -3
  • 示例代码如下:

public static void main(String[] args) {
        String s = "frank";
        int key = 3;
        String encriptData = KaiserEncrypt(s, key);
        System.out.println("encriptData="+encriptData);
        String decriptData = KaiserDecrypt(encriptData, key);
        System.out.println("decriptData="+decriptData);
        /**
         * result as below:
         * encriptData=iudqn
         * decriptData=frank
         */
    }

    /**
     *
     * @param orignal:原文
     * @param key:秘钥
     * @return:返回值
     */
    private static String KaiserEncrypt(String orignal, int key) {
        char[] chars = orignal.toCharArray();
        StringBuilder sb = new StringBuilder();
        for (char aChar : chars) {
            // 获取字符的ASCII编码
            int asciiCode = aChar;
            // 偏移数据
            asciiCode += key;
            // 将偏移后的数据转为字符
            char result = (char) asciiCode;
            // 拼接数据
            sb.append(result);
        }
        return sb.toString();
    }

    /**
     *
     * @param encryptedData 密文
     * @param key 秘钥
     * @return
     */
    private static String KaiserDecrypt(String encryptedData, int key) {
        char[] chars = encryptedData.toCharArray();
        StringBuilder sb = new StringBuilder();
        for (char aChar : chars) {
            int asciiCode = aChar;
            asciiCode -= key;
            char result = (char) asciiCode;
            sb.append(result);
        }
        return sb.toString();
    }

频度分析法破解恺撒加密

  • 将明文字母的出现频率与密文字母的频率相比较的过程
  • 通过分析每个符号出现的频率而轻易地破译代换式密码
  • 在每种语言中,冗长的文章中的字母表现出一种可对之进行分辨的频率。
  • e是英语中最常用的字母,其出现频率为八分之一
  • 由于破解太简单,在此不贴代码了,有兴趣的自己试试

对称加密

以分组为单位进行处理的密码算法称为分组密码(blockcipher) Logo

  • 对称加密采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密,也称为单密钥加密。

  • 示例

    • 我们现在有一个原文3要发送给B
    • 设置密钥为108, 3 * 108 = 324, 将324作为密文发送给B
    • B拿到密文324后, 使用324/108 = 3 得到原文
  • 常见对称加密算法

    • DES : Data Encryption Standard,即数据加密标准,是一种使用密钥加密的块算法,1977年被美国联邦政府的国家标准局确定为联邦资料处理标准(FIPS),并授权在非密级政府通信中使用,随后该算法在国际上广泛流传开来。
    • AES : Advanced Encryption Standard, 高级加密标准 .在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。
  • 特点

    • 加密速度快, 可以加密大文件
    • 密文可逆, 一旦密钥文件泄漏, 就会导致数据暴露
    • 加密后编码表找不到对应字符, 出现乱码
    • 一般结合Base64使用
  • 分组密码的模式

    1. 按位异或

      • 第一步需要将数据转换为二进制

      • 按位异或操作符: ^

      • 两个标志位进行按位异或操作:

        • 相同为0, 不同为1
      • 举例:

        1 0 0 0   ----> 8
        1 0 1 1   ----> 11
        -----------------------按位异或一次
        0 0 1 1   ---->  3
        1 0 1 1   ----> 11
        -----------------------按位异或两侧
        1 0 0 0   -----> 8
        =================================
        a = 8
        b = 11
        a  b按位异或1次 ==> 加密
        得到的结果再次和 b 按位异或 ===> 解密
    2. ECB - Electronic Code Book, 电子密码本模式

      • 特点: 简单, 效率高, 密文有规律, 容易被破解
      • 最后一个明文分组必须要填充
        • des/3des -> 最后一个分组填充满8字节
        • aes -> 最后一个分组填充满16字节
      • 不需要初始化向量
    3. CBC - Cipher Block Chaining, 密码块链模式

      • 特点: 密文没有规律, 经常使用的加密方式
      • 最后一个明文分组需要填充
        • des/3des -> 最后一个分组填充满8字节
        • aes -> 最后一个分组填充满16字节
      • 需要一个初始化向量 - 一个数组
        • 数组的长度: 与明文分组相等
        • 数据来源: 负责加密的人的提供的
        • 加解密使用的初始化向量值必须相同
    4. CFB - Cipher FeedBack, 密文反馈模式

      • 特点: 密文没有规律, 明文分组是和一个数据流进行的按位异或操作, 最终生成了密文
      • 需要一个初始化向量 - 一个数组
        • 数组的长度: 与明文分组相等
        • 数据来源: 负责加密的人的提供的
        • 加解密使用的初始化向量值必须相同
      • 不需要填充
    5. OFB - Output-Feedback, 输出反馈模式

      • 特点: 密文没有规律, 明文分组是和一个数据流进行的按位异或操作, 最终生成了密文
      • 需要一个初始化向量 - 一个数组
        • 数组的长度: 与明文分组相等
        • 数据来源: 负责加密的人的提供的
        • 加解密使用的初始化向量值必须相同
      • 不需要填充
    6. CTR - CounTeR, 计数器模式

      • 特点: 密文没有规律, 明文分组是和一个数据流进行的按位异或操作, 最终生成了密文
      • 不需要初始化向量
      • 不需要填充
    7. 最后一个明文分组的填充

      • 使用cbc, ecb需要填充
        • 要求:
          • 明文分组中进行了填充, 然后加密
          • 解密密文得到明文, 需要把填充的字节删除
      • 使用 ofb, cfb, ctr不需要填充
    8. 初始化向量 - IV

      • ecb, ctr模式不需要初始化向量
      • cbc, ofc, cfb需要初始化向量
        • 初始化向量的长度
          • des/3des -> 8字节
          • aes -> 16字节
        • 加解密使用的初始化向量相同
  • DES加密示例代码如下:

    • DES加密,key的大小必须是8个字节
    • 如果没有指定分组密码模式和填充模式,ECB/PKCS5Padding就是默认值
    • 如果指定分组密码模式为CBC,必须指定初始向量,初始向量中密钥的长度必须是8个字节
    • NoPadding模式,原文的长度必须是8个字节的整倍数
public class DESDemo {
    // DES加密算法,key的大小必须是8个字节
    public static void main(String[] args) throws Exception {
        String input = "frank";
        String key = "12345678";
        // 指定获取Cipher的算法,如果没有指定分组密码模式和填充模式,ECB/PKCS5Padding就是默认值
        // CBC模式,必须指定初始向量,初始向量中密钥的长度必须是8个字节
        // NoPadding模式,原文的长度必须是8个字节的整倍数
//        String transformation = "DES";
        String transformation = "DES/CBC/PKCS5Padding";
        // 指定获取密钥的算法
        String algorithm = "DES";
        String encryptDES = DESEncrypt(input, key, transformation, algorithm);
        System.out.println("加密:" + encryptDES);
        String s = DESDecrypt(encryptDES, key, transformation, algorithm);
        System.out.println("解密:" + s);
    }

    /**
     *
     * @param input  明文
     * @param key   密钥(DES,密钥的长度必须是8个字节)
     * @param transformation  获取Cipher对象的算法
     * @param algorithm   获取密钥的算法
     * @return  返回密文
     * @throws Exception
     */
    private static String DESEncrypt(String input, String key, String transformation, String algorithm) throws Exception {
        // 1,获取Cipher对象
        Cipher cipher = Cipher.getInstance(transformation);
        // 指定密钥规则
        SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algorithm);
        // 2.初始化向量的秘钥长度需要根据算法而定,des 8个字节长度  aes 16个字节长度
        //这里为了方便,统一使用传来的秘钥
        IvParameterSpec iv = new IvParameterSpec(key.getBytes());
        cipher.init(Cipher.ENCRYPT_MODE, sks, iv);
//        cipher.init(Cipher.ENCRYPT_MODE, sks);
        // 3. 加密
        byte[] bytes = cipher.doFinal(input.getBytes());
        // 对数据进行Base64编码
        String encode = Base64.encode(bytes);
        return encode;
    }

   
    private static String DESDecrypt(String input, String key, String transformation, String algorithm) throws Exception {
        Cipher cipher = Cipher.getInstance(transformation);
        SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algorithm);
        IvParameterSpec iv = new IvParameterSpec(key.getBytes());
        cipher.init(Cipher.DECRYPT_MODE, sks, iv);
//         cipher.init(Cipher.DECRYPT_MODE, sks);
        byte[] bytes = cipher.doFinal(Base64.decode(input));
        return new String(bytes);
    }
}
  • AES加密示例代码如下(AES和DES加密高度类似)
    • AES加密,key的大小必须是16个字节
    • 如果没有指定分组密码模式和填充模式,ECB/PKCS5Padding就是默认值
    • 如果没有指定分组密码模式为CBC,必须指定初始向量,初始向量中密钥的长度必须是16个字节
    • NoPadding模式,原文的长度必须是16个字节的整倍数
public class AESDemo {
    // AES加密算法,key的大小必须是16个字节
    public static void main(String[] args) throws Exception {
        String input = "frank";
        String key = "1234567887654321";
        // 指定获取Cipher的算法,如果没有指定分组密码模式和填充模式,ECB/PKCS5Padding就是默认值
        // CBC模式,必须指定初始向量,初始向量中密钥的长度必须是16个字节
        // NoPadding模式,原文的长度必须是16个字节的整倍数
//        String transformation = "AES";
        String transformation = "AES/CBC/PKCS5Padding";
        // 指定获取密钥的算法
        String algorithm = "AES";
        String encryptAES = AESEncrypt(input, key, transformation, algorithm);
        System.out.println("加密:" + encryptAES);
        String s = AESDecrypt(encryptAES, key, transformation, algorithm);
        System.out.println("解密:" + s);
    }

    /**
     *
     * @param input  明文
     * @param key   密钥(AES,密钥的长度必须是16个字节)
     * @param transformation  获取Cipher对象的算法
     * @param algorithm   获取密钥的算法
     * @return  返回密文
     * @throws Exception
     */
    private static String AESEncrypt(String input, String key, String transformation, String algorithm) throws Exception {
        // 1,获取Cipher对象
        Cipher cipher = Cipher.getInstance(transformation);
        // 指定密钥规则
        SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algorithm);
        // 2.初始化向量的秘钥长度需要根据算法而定,des 8个字节长度  aes 16个字节长度
        //这里为了方便,统一使用传来的秘钥
        IvParameterSpec iv = new IvParameterSpec(key.getBytes());
        cipher.init(Cipher.ENCRYPT_MODE, sks, iv);
//        cipher.init(Cipher.ENCRYPT_MODE, sks);
        // 3. 加密
        byte[] bytes = cipher.doFinal(input.getBytes());
        // 对数据进行Base64编码
        String encode = Base64.encode(bytes);
        return encode;
    }


    private static String AESDecrypt(String input, String key, String transformation, String algorithm) throws Exception {
        Cipher cipher = Cipher.getInstance(transformation);
        SecretKeySpec sks = new SecretKeySpec(key.getBytes(), algorithm);
        IvParameterSpec iv = new IvParameterSpec(key.getBytes());
        cipher.init(Cipher.DECRYPT_MODE, sks, iv);
//         cipher.init(Cipher.DECRYPT_MODE, sks);
        byte[] bytes = cipher.doFinal(Base64.decode(input));
        return new String(bytes);
    }
}

非对称加密

1. 对称加密的弊端

  • 秘钥分发困难

  • 可以通过非对称加密完成秘钥的分发

    https

    Alice 和 Bob通信, Alice给bob发送数据, 使用对称加密的方式

    1. 生成一个非对称的秘钥对, bob生成
    2. bob将公钥发送给alice
    3. alice生成一个用于对称加密的秘钥
    4. alice使用bob的公钥就对称加密的秘钥进行加密, 并且发送给bob
    5. bob使用私钥就数据解密, 得到对称加密的秘钥
    6. 通信的双方使用写好的秘钥进行对称加密数据加密

2. 非对称加密的秘钥

  • 不存在秘钥分发困难的问题

2.1 场景分析

数据对谁更重要, 谁就拿私钥

  • 直观上看: 私钥比公钥长
  • 使用第三方工具生成密钥对: 公钥文件xxx.pub xxx
  1. 通信流程, 信息加密 (A写数据, 发送给B, 信息只允许B读)

    A: 公钥

    B: 私钥

  2. 登录认证 (客户端要登录, 连接服务器, 向服务器请求个人数据)

    客户端: 私钥

    服务器: 公钥

  3. 数字签名(表明信息没有受到伪造,确实是信息拥有者发出来的,附在信息原文的后面)

    • 发送信息的人: 私钥
    • 收到信息的人: 公钥
  4. 网银U盾

    • 个人: 私钥
    • 银行拿公钥

3. 使用RSA非对称加密通信流程

要求: Alice 给 bob发送数据, 保证数据信息只有bob能看到

4. 生成RSA的秘钥对

4.1 一些概念

  1. x509证书规范、pem、base64
    • pem编码规范 - 数据加密
    • base64 - 对数据编码, 可逆
      • 不管原始数据是什么, 将原始数据使用64个字符来替代
        • a-z A-Z 0-9 + /
  2. ASN.1抽象语法标记
  3. PKCS1标准

5. 常见算法

RSA

ECC(java需要借助第三方库实现加密解密,我将在另外一个项目中用go实现)

###6 RSA示例代码如下

  • 注意:经测试在后端使用Cipher.getInstance(“RSA”)加密,在移动端获取解密的Cipher类时要使用Cipher.getInstance(“RSA/ECB/PKCS1Padding”)

  • 移动端核心代码

  //后端用私钥加密过的数据
    String eText = "ETLPedgx7vR9E9JGNIj4pLhvurcOM26oo4RgJhmeF5RvDXVdl3qQ+5H6hmUx3dV2K8jPxi5aKSVs1xnjuMgSfK32fjrqzYBULFaBCmnN1HbpcwYFNMA3enWiVwT3TAWFKA9ReJ2DWh0lkCzaHruOmcWCY3f2tjuEE9X9L0DN7m5R9iy2qgEEDPgfzImYYl8wltYdudryz2fQ7UNGdIUPc75EMqdvHEUrxIi5A7cM0BDGQI2aXD+39ijQCOVBtai/9ohF7YXtGmbsocPBKarhe8qpVIcvXza6fBbOWxBC6Z68uRGcljTVkhvNjWrEmRuu5pc3C41bx4OK9FD8kPgITg==";
    //后端给的公钥
    String pkey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmpz9g5IX3ElRtXFo2+9nwD4+amqhEH4Rz1FI2cXeSiQeLPfdCoSsflLovdJ21NxvcKGw9IvmjWkLESCVU/pxDeP2UkVXFAjC2KhZvoQO4v0x4Yn3/55bAAQ9O3qoGatjPlDbzr1CEAi+ZA7NY1Oz2TtOSq8Odc7wc3Sq6U1gZBf87w5jq0GwQwgLrQjaVf5oTgKmavyf6g8Uq8U0QnktXCJpJUsSSZdeWTwAhtKk+MDkd5VRHIynLklOgeAhjG7xzEAad/Q32qLGcCwY+ySiZWLZ5q5uZAys4rj98LiwV6zLyk8CYYclUDUtBPLLXDRN8DUEe4uKAucFC4IlkrXQ0wIDAQAB";
    //获取公钥对象
    KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
    X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.decode(pkey, Base64.DEFAULT));
    PublicKey publicKey = keyFactory.generatePublic(spec);
    //指定解密algorithm = "RSA/ECB/PKCS1Padding"; Cipher cipher = Cipher.getInstance(algorithm);
    algorithm = "RSA/ECB/PKCS1Padding";
    String decrypt = RSADemo.RSADecrypt(algorithm, publicKey, eText, 256);
    System.out.println("decript###" + decrypt + "###");
  • 后端代码
public class RSADemo {
    private static int MAX_ENCRIPT_SIZE = 200;

    public static void main(String[] args) throws Exception {
        String algorithm = "RSA";
        String input = "frank";
//        generateKeys(algorithm, "test.pri", "test.pub");

        PrivateKey privateKey = getPrivateKey("test.pri", algorithm);
        PublicKey publicKey = getPublicKey("test.pub", algorithm);

        String encryptData = RSAEncrypt(algorithm, privateKey, input, MAX_ENCRIPT_SIZE);
        System.out.println("encryptData=" + encryptData);
        String decryptData = RSADecrypt(algorithm, publicKey, encryptData, 256);
        System.out.println("decryptData=" + decryptData);

    }

    public static PrivateKey getPrivateKey(String priPath, String algorithm) throws Exception {
        String privateKeyString = FileUtils.readFileToString(new File(priPath), Charset.defaultCharset());
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.decode(privateKeyString));
        return keyFactory.generatePrivate(spec);
    }

    public static PublicKey getPublicKey(String pubPath, String algorithm) throws Exception {
        String publicKeyString = FileUtils.readFileToString(new File(pubPath), Charset.defaultCharset());
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.decode(publicKeyString));
        return keyFactory.generatePublic(spec);
    }

    public static void generateKeys(String algorithm, String priPath, String pubPath) throws Exception {
        // 获取密钥对生成器
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
        // 获取密钥对
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        // 获取公私钥
        PrivateKey privateKey = keyPair.getPrivate();
        PublicKey publicKey = keyPair.getPublic();
        // 获取公私钥的字节数组
        byte[] privateKeyEncoded = privateKey.getEncoded();
        byte[] publicKeyEncoded = publicKey.getEncoded();
        // 对公私钥进行Base64的编码
        String privateKeyString = Base64.encode(privateKeyEncoded);
        String publicKeyString = Base64.encode(publicKeyEncoded);

        FileUtils.writeStringToFile(new File(priPath), privateKeyString, Charset.defaultCharset());
        FileUtils.writeStringToFile(new File(pubPath), publicKeyString, Charset.defaultCharset());
    }

    public static String RSAEncrypt(String algorithm, Key key, String input, int maxEncryptSize) throws Exception {
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] data = input.getBytes();
        int total = data.length;
        int offset = 0;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] bytes;
        while (total - offset > 0) {
            if (total - offset >= maxEncryptSize) {
                bytes = cipher.doFinal(data, offset, maxEncryptSize);
                offset += maxEncryptSize;
            } else {
                bytes = cipher.doFinal(data, offset, total - offset);
                offset = total;
            }
            baos.write(bytes);
        }
        return Base64.encode(baos.toByteArray());
    }

    public static String RSADecrypt(String algorithm, Key key, String input, int maxEncryptSize) throws Exception {
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.DECRYPT_MODE, key);
        byte[] data = Base64.decode(input);
        int total = data.length;
        int offset = 0;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] bytes;
        while (total - offset > 0) {
            if (total - offset >= maxEncryptSize) {
                bytes = cipher.doFinal(data, offset, maxEncryptSize);
                offset += maxEncryptSize;
            } else {
                bytes = cipher.doFinal(data, offset, total - offset);
                offset = total;
            }
            baos.write(bytes);
        }
        return baos.toString();
    }
}

由于篇幅有点长,我将在另外一篇介绍哈希函数和数字签名

Releases

No releases published

Packages

No packages published

Languages