Cipher加密

Author Avatar
kevin
发表:2023-01-09 13:39:00
修改:2024-10-09 13:39:55

常见加密方式

对称加密

采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密

常见加密算法

  • DES
    • 数据加密
    • 加密的key必须是8位
  • AES(常用)
    • 高级加密
    • DES加强版,key必须是16位

由于加密后有负数,所以一般结合Base64转码使用(后面会解释)

DES

/**
 * ClassName: DesDemo
 * Description:
 * date: 2021/12/10 14:30
 * des和aes是一样的,把算法和转换换成aes即可,其他不用改
 * 由于输出有负数,所以需要使用base64转码
 * @author Yee
 * @since JDK 1.8
 */
public class DesDemo {
    public static void main(String[] args) throws Exception{

        //加密原文
        String input = "叶生";
        //key,如果使用的是des,必须是8位
        String key = "13579246";
        //要使用的算法
        String algorithm = "DES";
        //要转换成什么加密格式
        String transformation = "DES";
        //调用加密方法
        String encrypt = encryptDES(input, key, algorithm, transformation);
        System.out.println("加密之后:"+encrypt);
        //调用解密方法
        String decrypt = decryptDES(encrypt, key, algorithm, transformation);
        System.out.println("解密之后:"+decrypt);
    }

    /**
     * 抽取加密方法
     * @param input  原文
     * @param key    密钥,DES密钥长度必须是8个字符
     * @param algorithm  密钥算法
     * @param transformation   Cipher对象的算法
     * @return   返回密文
     * @throws Exception
     */
    private static String encryptDES(String input, String key, String algorithm, String transformation) throws Exception {
        //加密对象,需要传入一个加密格式
        Cipher cipher = Cipher.getInstance(transformation);
        //指定密钥规则,需要传入key的字节数组和加密的算法
        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), algorithm);
        /**
         * 初始化密码,根据选择的模式判断是加密还是解密
         * 第二个参数是密钥
         */
        cipher.init(Cipher.ENCRYPT_MODE,keySpec);
        //进行加密
        byte[] bytes = cipher.doFinal(input.getBytes());
        //由于加密后字节码有负数无法显示
        //需要使用base64转码可显示加密内容
        String encode = Base64.encode(bytes);
        return encode;
    }

    /**
     * 抽取解密方法
     * @param input    密文
     * @param key     密钥
     * @param algorithm   密钥算法
     * @param transformation   cipher算法
     * @return    返回原文
     * @throws Exception
     */
    private static String decryptDES(String input, String key, String algorithm, String transformation) throws Exception{
        //获得解密对象
        Cipher cipher = Cipher.getInstance(transformation);
        //密钥规则
        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), algorithm);
        //密码初始化
        cipher.init(Cipher.DECRYPT_MODE,keySpec);
        //解密
        byte[] bytes = cipher.doFinal(Base64.decode(input));
        return new String(bytes);
    }
}

AES

AES 加密解密和 DES 加密解密代码一样

修改加密算法就行

修改的代码是,其他地方不变

 // AES加密算法,比较高级,所以key的大小必须是16个字节
        String key = "1234567812345678";
  		String transformation = "AES";
        // 指定获取密钥的算法
        String algorithm = "AES";
        // 先测试加密,然后在测试解密

Base64

Base64可以将负数转为62个常见字母和斜杠和加号

Base64 算法原理

3个字节为一组,一个字节 8位,一共 就是24位

把3个字节转成4组,每组6位,不足8位,高位补0

这样做的好处在于base取的是后面6位,去掉高2位 ,

那么base64的取值就可以控制在0-63位了

Base64构成

  • 小写 a - z 26个字母
  • 大写 A - Z 26个字母
  • 数字 0 - 9 10个数字
  • + / 两个字符

一个是64个,所以叫Base64

由于三个字节一组,不足位数,会使用等号补齐

消息摘要

也称为数字摘要

无论输入的消息有多长,计算出来的消息摘要的长度总是固定的

比如MD5算法摘要的消息有128个比特位,用SHA-1算法摘要的消息最终有160比特位的输出

但相同的输入必会产生相同的输出,弊端,超大型数据库可以存储常见的密码,

因为相同的原文总是计算出固定的密文,所以不安全,现在作用于文件加密

常见的算法

  • MD5(常用)
  • SHA256
  • SHA512(常用)

消息摘要不是使用base64进行编码的,所以我们需要把值转成16进制

/**
 * ClassName: DigestDemo
 * Description:
 * date: 2021/12/10 18:58
 * 信息摘要,不需要使用base64,使用16进制替换负数
 *
 * @author Yee
 * @since JDK 1.8
 */
public class DigestDemo {
    public static void main(String[] args) throws Exception {
        //原文
        String input = "abc";
        //加密算法,填入算法名即可
        String algorithm = "MD5";
        //获得信息摘要
        MessageDigest instance = MessageDigest.getInstance(algorithm);
        //获得字节数组
        byte[] digest = instance.digest(input.getBytes());
        //转成base64和md5不合适,使用16进制,去掉高位
        //留最低8位,可以解决负数问题
        //拼接md5
        StringBuffer sb = new StringBuffer();
        for (byte b : digest) {
            String str = Integer.toHexString(b & 0xff);
            //不足两位补0,否则生成的md5长度缺失
            if (str.length() == 1){
                str = "0"+str;
            }
            sb.append(str);
        }
        System.out.println(sb.toString());
    }
}

文件消息摘要

因为相同的原文总是计算出固定的密文,所以不安全,现在作用于文件加密

网上下载软件有些提供sha512或者md5可以进行校验是否原版软件

/**
 * ClassName: FileDomo
 * Description:
 * date: 2021/12/10 19:41
 * 文件的信息摘要,由于查询文件有没有被更改
 *
 * @author Yee
 * @since JDK 1.8
 */
public class FileDemo {
    public static void main(String[] args) throws Exception {
        //加密方式
        String algorithm = "sha-512";
        //文件路径
        FileInputStream fis = new FileInputStream("F:\\apache-tomcat-8.5.73-windows-x64.zip");
        String file = getDigestFile(algorithm, fis);
        System.out.println("文件的sha-512:"+file);
    }

    private static String getDigestFile(String algorithm, FileInputStream fis) throws IOException, NoSuchAlgorithmException {
        //记录读取的长度
        int len ;
        //每次读1024字节
        byte[] buffer = new byte[1024];
        //字节流存储
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        while ((len = fis.read(buffer)) != -1){
            //要写入的数组,从0开始,到len长度
            outputStream.write(buffer,0,len);
        }
        //获得信息摘要
        MessageDigest instance = MessageDigest.getInstance(algorithm);
        // 在缓冲区的内容转换为字节数组
        byte[] digest = instance.digest(outputStream.toByteArray());
        return toHex(digest);
    }

    //信息摘要转为16进制
    private static String toHex(byte[] digest) {
        StringBuilder sb = new StringBuilder();
        for (byte b : digest) {
            String s = Integer.toHexString(b & 0xff);
            // 保持数据的完整性,前面不够的用0补齐
            if (s.length()==1){
                s="0"+s;
            }
            sb.append(s);
        }
        return sb.toString();
    }
}

摘要总结

  • MD5算法 : 摘要结果16个字节, 转16进制后32个字节
  • SHA256算法 : 摘要结果32个字节, 转16进制后64个字节
  • SHA512算法 : 摘要结果64个字节, 转16进制后128个字节

非对称加密

与对称加密算法不同,非对称加密算法需要两个密钥:公开密钥(publickey)私有密(privatekey)

因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法

  • 如果使用私钥加密, 只能使用公钥解密
  • 如果使用公钥加密, 只能使用私钥解密

常见算法

  • RSA(常用)
  • ECC

这里把密钥保存在本地,并把读取密钥方法作为静态方法方便调用

/**
 * ClassName: RSAdemo
 * Description:
 * date: 2021/12/10 22:24
 * 非对称加密算法
 * 公钥和私钥
 * @author Yee
 * @since JDK 1.8
 */
public class RSAdemo {
    public static void main(String[] args) throws Exception {

        //要加密的原文
        String input = "叶生";
        //加密算法
        String algorithm = "RSA";

        //保存密钥到本地,这里没加前缀,默认是根目录,没有该文件会自动创建
        generateKeyToFile(algorithm,"publicKey.pub","privateKey.pri");
        //读取本地私钥
      //  PrivateKey privateKey = getPrivateKey("privateKey.pri",algorithm);
        //读取本地公钥
       // PublicKey publicKey = getPublicKey("publicKey.pub",algorithm);
    }

    /**
     *  读取公钥
     * @param pulickPath  公钥路径
     * @param algorithm  算法
     * @return
     */
    public static PublicKey getPublicKey(String pulickPath, String algorithm) throws Exception {
        //文件转为字符串
        String publicKeyStr = FileUtils.readFileToString(new File(pulickPath), Charset.defaultCharset());
        //读取密钥工厂
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        //base64解码,这个类表示私钥,是一个规则,专用于公钥转码
        X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.decode(publicKeyStr));
        //生成公钥,传入规则
        return keyFactory.generatePublic(spec);
    }


    /**
     *  读取私钥
     * @param priPath   密钥路径
     * @param algorithm  算法
     * @return  返回私钥
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(String priPath,String algorithm) throws Exception {
        //将文件内容转为字符串,按照默认方式,怎么存的,怎么取
        String privateKeyStr = FileUtils.readFileToString(new File(priPath), Charset.defaultCharset());
        //获取密钥工厂
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        //64解码,这个类表示私钥,是一个规则,专用于私钥转码
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.decode(privateKeyStr));
        //生成私钥,传入一个规则
        return keyFactory.generatePrivate(spec);
    }

    /**
     *
     * @param algorithm  加密算法
     * @param pubPath   公钥存储路径
     * @param priPath   私钥存储路径
     * @throws Exception
     */
    public static  void generateKeyToFile(String algorithm,String pubPath,String priPath) throws Exception {
        //密钥生成器
        KeyPairGenerator instance = KeyPairGenerator.getInstance(algorithm);
        //生成密钥对
        KeyPair keyPair = instance.generateKeyPair();
        //生成私钥
        PrivateKey privateKey = keyPair.getPrivate();
        //生成公钥
        PublicKey publicKey = keyPair.getPublic();
        //获得私钥字节
        byte[] privateKeyEncoded = privateKey.getEncoded();
        //获得公钥字节
        byte[] publicKeyEncoded = publicKey.getEncoded();
        //密钥进行转码
        String privateKeyStr = Base64.encode(privateKeyEncoded);
        String publickKeyStr = Base64.encode(publicKeyEncoded);
        /**
         *  保存公钥和私钥,使用工具类.pom已经导入
         * 1,保存的路径
         * 2,保存的数据
         * 3,编码的格式
         */
        FileUtils.writeStringToFile(new File(pubPath),publickKeyStr, Charset.forName("UTF-8"));
        FileUtils.writeStringToFile(new File(priPath),privateKeyStr, Charset.forName("UTF-8"));
    }

    /**
     *  加密
     * @param algorithm  算法
     * @param key   密钥
     * @param input   原文
     * @return   返回密文
     * @throws Exception
     */
    public static String encrypRSA(String algorithm, Key key, String input) throws Exception{
        //私钥加密,创建加密对象
        Cipher cipher = Cipher.getInstance(algorithm);
        //选择加密模式,和key
        cipher.init(Cipher.ENCRYPT_MODE,key);
        //私钥进行加密
        byte[] bytes = cipher.doFinal(input.getBytes());
        //64编码
       return Base64.encode(bytes);
    }

    /**
     *  解密
     * @param algorithm  算法
     * @param key     密钥
     * @param encrypted    密文
     * @return    返回原文
     * @throws Exception
     */
    public static String decryptRSA(String algorithm,Key key,String encrypted) throws Exception{
        Cipher cipher = Cipher.getInstance(algorithm);
        // 私钥进行解密
        cipher.init(Cipher.DECRYPT_MODE,key);
        // 由于密文进行了Base64编码, 在这里需要进行解码
//        byte[] decode = Base64.decode(encrypted);
        //密文解密
        byte[] bytes1 = cipher.doFinal(encrypted.getBytes());
        return new String(bytes1);
    }
}

数字签名

公钥数字签名

使用的是非对称加密和信息摘要结合使用

比如常见的ssl证书就是数字签名,用于网站https验证

常见的结合算法sha256withrsa和sha512withrsa

/**
 * ClassName: SignatureDemo
 * Description:
 * date: 2021/12/11 11:00
 * 数字签名,采用现在流行的sha-512和rsa
 * @author Yee
 * @since JDK 1.8
 */
public class SignatureDemo {
    public static void main(String[] args) throws Exception {
        //原文
        String input = "叶生";
        //密钥算法
        String algorithm = "RSA";
        //签名算法
        String singn = "sha256withrsa";
        //读取本地私钥,使用的是上个封装的方法
          PrivateKey privateKey = RSAdemo.getPrivateKey("privateKey.pri",algorithm);
        //读取本地公钥,使用的是上个封装的方法
         PublicKey publicKey =  RSAdemo.getPublicKey("publicKey.pub",algorithm);
         //生成签名,使用私钥生成
        String signatured = getSignature(input,singn,privateKey);
        //校验签名
        boolean flage = verifySignature(input,singn,publicKey,signatured);
    }

    /**
     * 校验签名
     * @param input   原文
     * @param algorithm  算法
     * @param publicKey  公钥
     * @param signatured  签名
     * @return
     */
    private static boolean verifySignature(String input, String algorithm, PublicKey publicKey, String signatured) throws Exception {
        //获取签名
        Signature signature = Signature.getInstance(algorithm);
       //初始化签名
        signature.initVerify(publicKey);
        //传入原文
        signature.update(input.getBytes());
        //校验签名
        return signature.verify(Base64.decode(signatured));
    }

    /**
     *  生成签名
     * @param input  原文
     * @param algorithm  算法
     * @param privateKey  私钥
     * @return
     */
    private static String getSignature(String input, String algorithm, PrivateKey privateKey) throws Exception {
    //获得签名对象
        Signature signature = Signature.getInstance(algorithm);
        //初始化签名
        signature.initSign(privateKey);
        //更新原文
        signature.update(input.getBytes());
        // 开始签名
        byte[] sign = signature.sign();
        //对签名就行64编码
        return Base64.encode(sign);
    }
}

评论