在你学习和工作中,经常会用到各种加密算法来保护自己的信息安全,也经常听到对称和非对称加密的概念,可是仔细回想一下,这个对称和非对称到底是怎么来的,怎么个对称法?就自己学习的知识,咋们就来细说一下。

为了理解加密算法,首先需要了解一下几个概念:

基本概念

密钥

密钥(yao 4声),不是秘钥! 百度百科的概念为:密钥是一种参数,它是在明文转换为密文或将密文转换为明文的算法中输入的参数。密钥分为对称密钥与非对称密钥。 说白了就是一种参数,比如一个字符串

String params = "YouareGreat6666";
公钥

通过算法得到的一个密钥对(即一个公钥和一个私钥),公钥是密钥对中公开的部分,对所有人都是可见的。

私钥

通过算法得到的一个密钥对(即一个公钥和一个私钥),私钥是密钥对中是非公开的部分,只有解密的人才可知。

通过算法得到的密钥对(公钥-私钥)能保证在世界范围内是独一的。使用这个密钥对的时候,如果用其中一个密钥加密一段数据,必须用另一个密钥解密。比如用公钥加密数据就必须用私钥解密,如果用私钥加密也必须用公钥解密,否则解密将不会成功。
虽然公钥-私钥对是唯一的,但是我们可以创建无数组公钥-私钥对。

加密算法

什么是加密算法,首先加密算法需要满足两个基本的条件:

1.通过转换需要把明文(人能看得懂的信息) 转成 密文(人看不懂的信息);
2.能将密文给转回来,与之前明文信息一致。

所以当有人说MD5是不是加密算法,我个人认为应该不是的,因为MD5算法不可逆,转成了密文,大罗神仙都转不回来了,当然你抬杠要说使用彩虹表碰撞完全可以得到了,算我没说。

对称加密

在A和B通信过程中,使用同一个密钥。A给B的密文是 通过加密算法M + 密钥 加密算成 密文, B获取到密文中,使用解密算法N + 密钥的 逆过程,将密文解析回来。大体的图示为:

密钥AES_128 密钥为什么不读yao_对称加密&非非对称加密


基本上看图就很容易理解了,就不多说了。

常用算法

DES 56位密钥,现在已经废弃了。

AES 128位 192位 256位都有,现在也比较流行。

下面给出一个AES算法的实现:

public class AESUtil {

    private static final String defaultCharset = "UTF-8";
    private static final String KEY_AES = "AES";
    private static final String KEY = "YouareGreat6666";
    /**
     * 加密
     *
     * @param data 需要加密的内容
     * @param key 加密密码
     * @return
     */
    public static String encrypt(String data, String key) {
        return doAES(data, key, Cipher.ENCRYPT_MODE);
    }

    /**
     * 解密
     *
     * @param data 待解密内容
     * @param key 解密密钥
     * @return
     */
    public static String decrypt(String data, String key) {
        return doAES(data, key, Cipher.DECRYPT_MODE);
    }

    /**
     * 加解密
     *
     * @param data 待处理数据
     * @param key  密钥
     * @param mode 加解密mode
     * @return
     */
    private static String doAES(String data, String key, int mode) {
        try {
            if (isBlank(data) || isBlank(key)) {
                return null;
            }
            //判断是加密还是解密
            boolean encrypt = mode == Cipher.ENCRYPT_MODE;
            byte[] content;
            //true 加密内容 false 解密内容
            if (encrypt) {
                content = data.getBytes(defaultCharset);
            } else {
                content = parseHexStr2Byte(data);
            }
            //1.构造密钥生成器,指定为AES算法,不区分大小写
            KeyGenerator kgen = KeyGenerator.getInstance(KEY_AES);
            //2.根据ecnodeRules规则初始化密钥生成器
            //生成一个128位的随机源,根据传入的字节数组
            kgen.init(128, new SecureRandom(key.getBytes()));
            //3.产生原始对称密钥
            SecretKey secretKey = kgen.generateKey();
            //4.获得原始对称密钥的字节数组
            byte[] enCodeFormat = secretKey.getEncoded();
            //5.根据字节数组生成AES密钥
            SecretKeySpec keySpec = new SecretKeySpec(enCodeFormat, KEY_AES);
            //6.根据指定算法AES自成密码器
            Cipher cipher = Cipher.getInstance(KEY_AES);// 创建密码器
            //7.初始化密码器,第一个参数为加密(Encrypt_mode)或者解密解密(Decrypt_mode)操作,第二个参数为使用的KEY
            cipher.init(mode, keySpec);// 初始化
            byte[] result = cipher.doFinal(content);
            if (encrypt) {
                //将二进制转换成16进制
                return parseByte2HexStr(result);
            } else {
                return new String(result, defaultCharset);
            }
        } catch (Exception e) {
            System.out.println("error occured" + e);
        }
        return null;
    }

    /**
     * 将二进制转换成16进制
     *
     * @param buf
     * @return
     */
    public static String parseByte2HexStr(byte buf[]) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < buf.length; i++) {
            String hex = Integer.toHexString(buf[i] & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            sb.append(hex.toUpperCase());
        }
        return sb.toString();
    }
    /**
     * 将16进制转换为二进制
     *
     * @param hexStr
     * @return
     */
    public static byte[] parseHexStr2Byte(String hexStr) {
        if (hexStr.length() < 1) {
            return null;
        }
        byte[] result = new byte[hexStr.length() / 2];
        for (int i = 0; i < hexStr.length() / 2; i++) {
            int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
            int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);
            result[i] = (byte) (high * 16 + low);
        }
        return result;
    }
    
    private static boolean isBlank(String key) {
        return null == key || key.trim().length() == 0;
    }

然后可以尝试,进行一下加密和解密:

public static void main(String[] args) throws Exception {
        String content = "{'phone':'18818881888','password':'12365478965'}";
        
        System.out.println("加密前:" + content);
        System.out.println("加密密钥和解密密钥:" + KEY);
        String encrypt = encrypt(content, KEY);
        System.out.println("加密后:" + encrypt);
        String decrypt = decrypt(encrypt, KEY);
        System.out.println("解密后:" + decrypt);
    }

输出为:

密钥AES_128 密钥为什么不读yao_对称加密&amp;非非对称加密_02

对称加密的缺点
  1. 通信过程中密钥不能备泄漏,如果泄漏了,因为对称算法是固定的,那么加密通信将会失败。
对称加密的破解方式

通过上图的输出,我们可以知道,只要知道密钥了,那么系统就被破解了。通常的做法是 获取几组[明文 + 密文],然后通过尝试随机拼凑密钥的方式,通过算法使得明文加密后与密文一致,那么密钥就知道了。
解决的方式一般就是:

密钥尽可能复杂,让破解的时间变长。从时间的纬度上讲,对称加密始终会破解的。

非对称加密

A与B通信的过程中,使用的是同一套算法M,但是A发送给B的是 使用算法M + 公钥P 生成密文,B获取到密文之后,使用算法M + 私钥Q 解密出明文。大体图如下所示:

密钥AES_128 密钥为什么不读yao_加密算法_03


虽然看上去比较简单,但是其中实现原理比较复杂,其实存在比较高深的数学知识,我们拿过来就行了。

常用算法

下面提供一种RSA算法的Java例子:
首先你可能需要下载一个apache包

public class RSAEncrypt {

    private static Map<Integer, String> keyMap = new HashMap<Integer, String>();  //用于封装随机产生的公钥与私钥

    /**
     * 随机生成密钥对
     * @throws NoSuchAlgorithmException
     */
    public static void genKeyPair() throws NoSuchAlgorithmException {
        // KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
        // 初始化密钥对生成器,密钥大小为96-1024位
        keyPairGen.initialize(1024,new SecureRandom());
        // 生成一个密钥对,保存在keyPair中
        KeyPair keyPair = keyPairGen.generateKeyPair();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();   // 得到私钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();  // 得到公钥
        String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));
        // 得到私钥字符串
        String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));
        // 将公钥和私钥保存到Map
        keyMap.put(0,publicKeyString);  //0表示公钥
        keyMap.put(1,privateKeyString);  //1表示私钥
    }
    /**
     * RSA公钥加密
     *
     * @param str
     *            加密字符串
     * @param publicKey
     *            公钥
     * @return 密文
     * @throws Exception
     *             加密过程中的异常信息
     */
    public static String encrypt( String str, String publicKey ) throws Exception{
        //base64编码的公钥
        byte[] decoded = Base64.decodeBase64(publicKey);
        RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
        //RSA加密
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, pubKey);
        String outStr = Base64.encodeBase64String(cipher.doFinal(str.getBytes("UTF-8")));
        return outStr;
    }

    /**
     * RSA私钥解密
     *
     * @param str
     *            加密字符串
     * @param privateKey
     *            私钥
     * @return 铭文
     * @throws Exception
     *             解密过程中的异常信息
     */
    public static String decrypt(String str, String privateKey) throws Exception{
        //64位解码加密后的字符串
        byte[] inputByte = Base64.decodeBase64(str.getBytes("UTF-8"));
        //base64编码的私钥
        byte[] decoded = Base64.decodeBase64(privateKey);
        RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
        //RSA解密
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, priKey);
        String outStr = new String(cipher.doFinal(inputByte));
        return outStr;
    }

然后我们进行测试加密&解密:

public static void main(String[] args) throws Exception {
        //生成公钥和私钥
        genKeyPair();
        //加密字符串
        String message = "{'phone':'18818881888','password':'12365478965'}";
        System.out.println("随机生成的公钥为:" + keyMap.get(0));
        System.out.println("随机生成的私钥为:" + keyMap.get(1));

        String messageEn = encrypt(message,keyMap.get(0));
        System.out.println(message + "\n加密后的字符串为:" + messageEn);
        String messageDe = decrypt(messageEn,keyMap.get(1));

        System.out.println("还原后的字符串为:" + messageDe);
    }

运行结果如下:

密钥AES_128 密钥为什么不读yao_密钥AES_128_04


可以看出,私钥比公钥长太多了。

非对称加密算法的优缺点
  1. 效率比较低下,相比较于对称加密;
  2. 可靠性比较高,可以在不安全网络上传输密钥。

破解方式

与对称加密不同之处的是,非对称加密的公钥很容易获取,因此制造明文 --> 密文是没有难度的。所以非对称加密的关键在于,如果正确的获取到私钥,可以解密所有经过公钥加密过的密文。找到这样的私钥即为成功破解。但是由于非对称加密的自身特性,怎么样通过公钥来推断私钥通常是一种常见的思路,但是往往最佳手段依然是穷举法,只是和对称加密的方式不同区别在于:对称加密需要不断尝试新的密钥来破解明文 -> 密文来进行加密和解密,但是非对称加密需要不断尝试自己的新私钥来判断是否和公钥互相可解。

总结

最后一句话概括:对称加密使用同一个密钥,不同的算法;非对称家吗使用不同的密钥,同一套算法。所以我们可以理解为所谓对称,是针对密钥而言的。