加密入门-对称密码简介(Java实现)

一、手写简单加密算法(凯撒密码)

首先,我们需要知道几个基本概念:

  1. 明文:原始信息。
  2. 密钥:加密与解密算法的参数,直接影响对明文进行变换的结果。
  3. 密文:对明文进行变换的结果。
  4. 加密算法:以密钥为参数,对明文进行多种置换和转换的规则和步骤,变换结果为密文。
  5. 解密算法:加密算法的逆变换,以密文为输入、密钥为参数,变换结果为明文。

1. 凯撒密码介绍

​ 凯撒密码作为一种最为古老的加密技术,在古罗马的时候都已经很流行,他的基本思想是:通过把字母移动一定的位数来实现加密和解密。明文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移后被替换成密文。

​ 例如,当偏移量是 3 的时候,所有的字母 A 将被替换成 D,B 变成 E,由此可见,位数就是凯撒密码加密和解密的密钥。 ​ 字符串”ABC”的每个字符都右移 3 位则变成”DEF”,解密的时候”DEF”的每个字符左移 3 位即能还原,如下图所示:

加密入门-对称密码简介(Java实现)_第1张图片

文件目录结构:

加密入门-对称密码简介(Java实现)_第2张图片

CasarDemo.java

package com.itheima.encrypt;

public class CaesarDemo {
    public static void main(String[] args) {
        //1.明文原始信息
        String clearTest="heima";
        //2.加密规则:将字母按字母表的顺序向右移动3位
        int key=3;
        //通过加密算法将明文混要之后的信息
        String cipher=encrypt(clearTest,key);
        
        System.out.println(cipher);
        
        //解密规则:将字母按照字母表的顺序向左移动3位
        String clear=decrypt(cipher,key);
        System.out.println(clear);
    }
    
    private static String encrypt(String clearTest,int key) {
        char[] charArray=clearTest.toCharArray();
        //System.out.println(charArray);
        for(int i=0;i             charArray[i]+=key;
        }
        return new String(charArray);
    }
    private static String decrypt(String cipher,int key) {
        char[] charArray=cipher.toCharArray();
        //System.out.println(charArray);
        for(int i=0;i             charArray[i]-=key;
        }
        return new String(charArray);
    }
}
 

二.对称加密

1. 概述

​ 加密和解密都使用同一把密钥,这种加密方法称为对称加密,也称为单密钥加密。

​ 简单理解为:加密解密都是同一把钥匙。

​ 上节中学到的凯撒密码就属于对称加密,他的字符偏移量即为密钥。

​ 基于“对称密钥”的加密算法主要有DES算法,3DES算法,AES算法,Blowfish算法,RC5算法和IDEA算法等等,我们这里主要介绍DES算法和AES算法。

2.对称密码常用的数学运算

  对称密码当中有几种常用到的数学运算。

◆移位和循环移位  移位就是将一段数码按照规定的位数整体性地左移或右移。循环右移就是当右移时,把数码的最后的位移到                        数码的最前头,循环左移正相反。例如,对十进制数码12345678循环右移1位(十进制位)的结果为81234567,                          而循环左移1位的结果则为23456781。

 ◆置换  就是将数码中的某一位的值根据置换表的规定,用另一位代替。它不像移位操作那样整齐有序,看上去杂乱无章。这正                是加密所需,被经常应用。 

◆扩展  就是将一段数码扩展成比原来位数更长的数码。扩展方法有多种,例如,可以用置换的方法,以扩展置换表来规定扩展后的              数码每一位的替代值。 

◆压缩  就是将一段数码压缩成比原来位数更短的数码。压缩方法有多种,例如,也可以用置换的方法,以表来规定压缩后的数码             每一位的替代值。 

◆异或  这是一种二进制布尔代数运算。异或的数学符号为⊕ ,它的运算法则如下: ​ 1⊕1 = 0  ​ 0⊕0 = 0  ​ 1⊕0 = 1  ​ 0⊕1 = 1                也可以简单地理解为,参与异或运算的两数位如相等,则结果为0,不等则为1。 

◆迭代  迭代就是多次重复相同的运算,这在密码算法中经常使用,以使得形成的密文更加难以破解。

总结:这些运算主要是对比特位进行操作,其共同目的就是把被加密的明文数码尽可能深地打乱,从而加大破译的难度。

三.对称加密算法

1. DES算法

①.简介

​ DES是Data Encryption Standard(数据加密标准)的缩写。它是由IBM公司研制的一种对称密码算法,美国国家标准局于1977年公布把它作为非机要部门使用的数据加密标准,三十年来,它一直活跃在国际保密通信的舞台上,扮演了十分重要的角色。

​ DES是一个分组加密算法,典型的DES以64位为分组对数据加密,加密和解密用的是同一个算法。它的密钥长度是56位(因为每个第8 位都用作奇偶校验),密钥可以是任意的56位的数,其保密性依赖于密钥。

​ DES加密的算法框架大致如下:

  首先要生成一套加密密钥,从用户处取得一个64位长的密码口令,然后通过等分、移位、选取和迭代形成一套16个加密密钥,分别供每一轮运算中使用。

​ DES对64位(bit)的明文分组M进行操作,M经过一个初始置换IP,置换成m0。将m0明文分成左半部分和右半部分,各32位长。

​ 然后进行16轮完全相同的运算(迭代),在每一轮运算过程中数据与相应的密钥结合。

  经过16轮迭代后,左、右半部分合在一起经过一个末置换(数据整理),这样就完成了加密过程。  ​ 加密流程如图所示:

简单的流程图:

加密入门-对称密码简介(Java实现)_第3张图片

package com.itheima.encrypt;

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

public class DESDemo2 {
    public static void main(String[] args) throws Exception {
        /**
         * 1.明文
         * 2.提供原始密码:长度64,8个字节
         */
        String clearText ="Thanks heima";
        String originKey="123456";
        
        String cipherText=desEncript(clearText,originKey);
        System.out.println(cipherText);
        
    }
    
    /**
     * 用DES算法进行加密
     * Cipher
     * @param clearText
     * @param originKey
     * @return NoSuchAlgorithmException:没有此算法异常
     * @return NoSuchPaddingException:填充异常
     * @throws InvalidKeyException 
     * @throws BadPaddingException 
     * @throws IllegalBlockSizeException 
     */

    private static String desEncript(String clearText, String originKey)throws NoSuchAlgorithmException,NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException{
        //1.获取加密算法工具类对象
        Cipher cipher =Cipher.getInstance("DES");
        //2.对工具类对象进行初始化
        //mode:加密/解密模式
        //key:对原始秘钥处理之后的秘钥
        SecretKeySpec key=getKey(originKey);
        cipher.init(Cipher.ENCRYPT_MODE, key);
        
        //用加密工具类对象对明文进行加密--》密文
        byte[] doFinal = cipher.doFinal(clearText.getBytes());
        
        return new String(doFinal);
    }
    /**
     * 不论originkey多长,我们都要形成一个8个字节长度的原始秘钥
     * @param originKey
     * @return
     */
    private static SecretKeySpec getKey(String originKey) {
        //byte数组每个元素默认初始值为0
        byte[] buffer = new byte[8];
        //获取用户提供的原始秘钥字节数组
        byte[] originBytes=originKey.getBytes();
        //如果originBytes.length > 8,只要8个字节,如果没有超过8个字节,就用默认初始值来填充
        for(int i=0;i<8 && i             buffer[i]=originBytes[i];
        }
        SecretKeySpec key=new SecretKeySpec(buffer,"DES");
        return key;
    }
}
 

 

2. 用Base64编码来解决乱码问题

​ 加密后的结果是字节数组,这些被加密后的字节在码表(例如GBK、 UTF-8 码表)上找不到对应字符,会出现乱码,当乱码字符串再次转换为字节数组时,长度会变化,导致解密失败,所以转换后的数据是不安全的。

​ 使用 Base64 对字节数组进行编码,任何字节都能映射成对应的 Base64 字符,之后能恢复到字节数组,利于加密后数据的保存和传输,所以转换是安全的。

​ Base64码表如下:

加密入门-对称密码简介(Java实现)_第4张图片

a、当字符串字符个数为3的整数倍时:

​ 比如字符串“ABC”,其在计算机内存中的 十六进制表示为 41、42、43,十进制表示为“65” “66” “67”;二进制表示为

​ 01000001  01000010  01000011  将这三个二进制数依次取6bit

 010000/01 0100/0010 01/000011  就转换成了:  

​ 010000  010100 001001 000011,将这四个二进制数转换成十六制数为:

     10,14,9,3,十进制数位为16,20,9,3。对照上面的码表,分别查找出对应的字符为Q,U,J,D。

​ 也是就说字符串“ABC”经过BASE64编码后得 出“QUJD”。这是最简单的情况,即ASCII码字符数刚好可以被3整除。接着继续讨论余数为2、为1的情况。  

b、当字符串字符个数除以3余数为2时:

​ 比如字符串“ce”,其在内存中十六进制表示为63,65;十 进制表示表示99,101;二进制表示为  

​ 01100011 01100101 依次取6bit

 011000/11 0110/0101 这时,第3个字符不足6位,在后面补零,也就 是0101变成010100。转换结果为  

​ 011000 110110 010100  这3个二进 制数转换成十六制数为18,36,14;

十进制数位为24,54,20。对照码表 得出结果“Y2U”。编码后的字符个数不足4位,用“=”填充,最后编码得出“Y2U=”。

    c、当余数为1时:

​ 比如字符串“{”,其在内存中的十六进制表示为$7B,十进制为 123,二进制位表示为  

​ 01111011 依次取6bit 

​ 011110/11 补0后为

     011110/110000  转换结果为011110和110000。这两个二进制数转换成

     十六进制数为1E,30,十进制数为30,48。对照码表得出结果为“ew”,补上

     “=”,最后编码得出“ew= =”。解码也很简单,是编码的逆过程,即将每 个字符对照码表换算成6bit的二进制数,然后重组起来,按8位进行截取,得出原码。

Base64.java源码

package com.itheima.encrypt;
import java.io.*;   
     
public class Base64 {   
    private static char[] base64EncodeChars = new char[] {   
        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',   
        'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',   
        'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',   
        'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',   
        'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',   
        'o', 'p', 'q', 'r', 's', 't', 'u', 'v',   
        'w', 'x', 'y', 'z', '0', '1', '2', '3',   
        '4', '5', '6', '7', '8', '9', '+', '/' };   
    
    private static byte[] base64DecodeChars = new byte[] {   
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,   
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,   
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,   
    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,   
    -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,   
    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,   
    -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,   
    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 };   
    
    public static String encode(byte[] data) {   
        StringBuffer sb = new StringBuffer();   
        int len = data.length;   
        int i = 0;   
        int b1, b2, b3;   
        while (i < len) {   
            b1 = data[i++] & 0xff;   
            if (i == len)   
            {   
                sb.append(base64EncodeChars[b1 >>> 2]);   
                sb.append(base64EncodeChars[(b1 & 0x3) << 4]);   
                sb.append("==");   
                break;   
            }   
            b2 = data[i++] & 0xff;   
            if (i == len)   
            {   
                sb.append(base64EncodeChars[b1 >>> 2]);   
                sb.append(base64EncodeChars[((b1 & 0x03) << 4) | ((b2 & 0xf0) >>> 4)]);   
                sb.append(base64EncodeChars[(b2 & 0x0f) << 2]);   
                sb.append("=");   
                break;   
            }   
            b3 = data[i++] & 0xff;   
            sb.append(base64EncodeChars[b1 >>> 2]);   
            sb.append(base64EncodeChars[((b1 & 0x03) << 4) | ((b2 & 0xf0) >>> 4)]);   
            sb.append(base64EncodeChars[((b2 & 0x0f) << 2) | ((b3 & 0xc0) >>> 6)]);   
            sb.append(base64EncodeChars[b3 & 0x3f]);   
        }   
        return sb.toString();   
    }   
    
    public static byte[] decode(String str) throws UnsupportedEncodingException {   
        StringBuffer sb = new StringBuffer();   
        byte[] data = str.getBytes("US-ASCII");   
        int len = data.length;   
        int i = 0;   
        int b1, b2, b3, b4;   
        while (i < len) {   
            /* b1 */   
            do {   
                b1 = base64DecodeChars[data[i++]];   
            } while (i < len && b1 == -1);   
            if (b1 == -1) break;   
            /* b2 */   
            do {   
                b2 = base64DecodeChars[data[i++]];   
            } while (i < len && b2 == -1);   
            if (b2 == -1) break;   
            sb.append((char)((b1 << 2) | ((b2 & 0x30) >>> 4)));   
            /* b3 */   
            do {   
                b3 = data[i++];   
                if (b3 == 61) return sb.toString().getBytes("ISO-8859-1");   
                b3 = base64DecodeChars[b3];   
            } while (i < len && b3 == -1);   
            if (b3 == -1) break;   
            sb.append((char)(((b2 & 0x0f) << 4) | ((b3 & 0x3c) >>> 2)));   
            /* b4 */   
            do {   
                b4 = data[i++];   
                if (b4 == 61) return sb.toString().getBytes("ISO-8859-1");   
                b4 = base64DecodeChars[b4];   
            } while (i < len && b4 == -1);   
            if (b4 == -1) break;   
            sb.append((char)(((b3 & 0x03) << 6) | b4));   
        }   
        return sb.toString().getBytes("ISO-8859-1");   
    }   
}   

 加入Base64编码的DESDemo.java

package com.itheima.encrypt;

import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

public class DESDemo {
    public static void main(String[] args) throws Exception {
        /**
         * 1.明文
         * 2.提供原始密码:长度64,8个字节
         */
        String clearText ="heimanihao";
        String originKey="12345678333";
        
        String cipherText=desEncript(clearText,originKey);
        System.out.println(cipherText);
        
        String clearText2=desDecript(cipherText,originKey);
        System.out.println(clearText2);
    }
    
    private static String desDecript(String cipherText, String originKey) throws Exception {
        // TODO Auto-generated method stub
        Cipher cipher = Cipher.getInstance("DES");
        
        Key key=getKey(originKey);
        cipher.init(Cipher.DECRYPT_MODE, key);
        
        byte[] decode=Base64.decode(cipherText);
        byte[] doFinal = cipher.doFinal(decode);
        return new String(doFinal);
    }
    /**
     * 用DES算法进行加密
     * Cipher
     * @param clearText
     * @param originKey
     * @return NoSuchAlgorithmException:没有此算法异常
     * @return NoSuchPaddingException:填充异常
     * @throws InvalidKeyException 
     * @throws BadPaddingException 
     * @throws IllegalBlockSizeException 
     */

    private static String desEncript(String clearText, String originKey)throws NoSuchAlgorithmException,NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException{
        //1.获取加密算法工具类对象
        Cipher cipher =Cipher.getInstance("DES");
        //2.对工具类对象进行初始化
        //mode:加密/解密模式
        //key:对原始秘钥处理之后的秘钥
        SecretKeySpec key=getKey(originKey);
        cipher.init(Cipher.ENCRYPT_MODE, key);
        
        //用加密工具类对象对明文进行加密--》密文
        byte[] doFinal = cipher.doFinal(clearText.getBytes());
        
        //return new String(doFinal);
        String encode=Base64.encode(doFinal);
        return encode;
    }
    /**
     * 不论originkey多长,我们都要形成一个8个字节长度的原始秘钥
     * @param originKey
     * @return
     */
    private static SecretKeySpec getKey(String originKey) {
        //byte数组每个元素默认初始值为0
        byte[] buffer = new byte[8];
        //获取用户提供的原始秘钥字节数组
        byte[] originBytes=originKey.getBytes();
        //如果originBytes.length > 8,只要8个字节,如果没有超过8个字节,就用默认初始值来填充
        for(int i=0;i<8 && i             buffer[i]=originBytes[i];
        }
        SecretKeySpec key=new SecretKeySpec(buffer,"DES");
        return key;
    }
}
 

 加密入门-对称密码简介(Java实现)_第5张图片

3. 3DES和AES算法

①3DES算法

​ DES 的安全性首先取决于密钥的长度。密钥越长,破译者利用穷举法搜索密钥的难度就越大。DES采用64bit分组长度56bit密钥长度: 

​ 随着软硬件技术的发展,多核CPU、分布式计算、量子计算等理论的实现,DES在穷举方式的暴力攻击下还是相当脆弱的,因此很多人想办法用某种算法替代它,面对这种需求广泛被采用的有两种方案:  ​ 1. 设计一套全新的算法,例如AES,这个在后面会说到。  ​ 2. 为了保护已有软硬件的投资,仍使用DES,但使用多个密钥进行多次加密,这就是多重DES加密。

​ 例如后来演变出的 3-DES 算法使用了 3 个独立密钥(密钥长度为168bit)进行三重 DES 加密,这就比 DES 大大提高了安全性。如果 56 位 DES 用穷举搜索来破译需要 2∧56 次运算,而 3-DES 则需要 2∧112 次。

加密入门-对称密码简介(Java实现)_第6张图片

 

②AES算法

a.简介

​ 3DES缺点是算法运行相对较慢。因为原来DES是为70年代的硬件设计的,算法代码并不是很高效,而3DES是DES算法的3轮迭代,因此更慢。而且DES和3DES的分组大小都是64bit,3DES密钥长度却是168bit,处于加密效率和安全的考虑,需要更大的分组长度。于是AES应运而生。

​ Advanced Encryption Standard 高级加密标准,该标准是美国国家标准技术研究所于2001年颁布的。AES旨在取代DES成为广泛使用的标准,2006年AES已成为最流行的对称加密算法。

​ AES使用的分组大小为128bit,密钥长度可以为128、192、256 bit。最简单最常用的也就是 128 bit(16字节) 的密钥。

b.算法原理

​ AES 加密过程涉及到 4 种操作:字节替代(SubBytes)、行移位(ShiftRows)、列混淆(MixColumns)和轮密钥加(AddRoundKey)。

​ 解密过程分别为对应的逆操作。由于每一步操作都是可逆的,按照相反的顺序进行解密即可恢复明文。加解密中每轮的密钥分别由初始密钥扩展得到。算法中 16(byte)字节的明文、密文和轮密钥都以一个 4x4 的矩阵表示。 

 ​加密入门-对称密码简介(Java实现)_第7张图片

c.代码实现

​ AES的java实现比较简单,只需要把密码设置为16byte,然后将DES算法实现的java代码中,将DES修改为AES即可。

package com.itheima.encrypt;

import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

public class AESDemo {
    public static void main(String[] args) throws Exception {
        /**
         * 1.明文
         * 2.提供原始密码:长度128,16个字节
         */
        String clearText ="zhangjunjie";
        String originKey="1234567887654321";
        
        String cipherText=aesEncript(clearText,originKey);
        System.out.println(cipherText);
        
        String clearText2=aesDecript(cipherText,originKey);
        System.out.println(clearText2);
    }
    
    private static String aesDecript(String cipherText, String originKey) throws Exception {
        // TODO Auto-generated method stub
        Cipher cipher = Cipher.getInstance("aes");
        
        Key key=getKey(originKey);
        cipher.init(Cipher.DECRYPT_MODE, key);
        
        byte[] decode=Base64.decode(cipherText);
        byte[] doFinal = cipher.doFinal(decode);
        return new String(doFinal);
    }
    /**
     * 用aes算法进行加密
     * Cipher
     * @param clearText
     * @param originKey
     * @return NoSuchAlgorithmException:没有此算法异常
     * @return NoSuchPaddingException:填充异常
     * @throws InvalidKeyException 
     * @throws BadPaddingException 
     * @throws IllegalBlockSizeException 
     */

    private static String aesEncript(String clearText, String originKey)throws NoSuchAlgorithmException,NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException{
        //1.获取加密算法工具类对象
        Cipher cipher =Cipher.getInstance("aes");
        //2.对工具类对象进行初始化
        //mode:加密/解密模式
        //key:对原始秘钥处理之后的秘钥
        SecretKeySpec key=getKey(originKey);
        cipher.init(Cipher.ENCRYPT_MODE, key);
        
        //用加密工具类对象对明文进行加密--》密文
        byte[] doFinal = cipher.doFinal(clearText.getBytes());
        
        //return new String(doFinal);
        String encode=Base64.encode(doFinal);
        return encode;
    }
    /**
     * 不论originkey多长,我们都要形成一个16个字节长度的原始秘钥
     * @param originKey
     * @return
     */
    private static SecretKeySpec getKey(String originKey) {
        //byte数组每个元素默认初始值为0
        byte[] buffer = new byte[16];
        //获取用户提供的原始秘钥字节数组
        byte[] originBytes=originKey.getBytes();
        //如果originBytes.length > 8,只要8个字节,如果没有超过8个字节,就用默认初始值来填充
        for(int i=0;i<16 && i             buffer[i]=originBytes[i];
        }
        SecretKeySpec key=new SecretKeySpec(buffer,"aes");
        return key;
    }
}
 

 

四.总结

到这里,我们的课程也即将告一段落了。最后大致的总结一下三种对称加密算法:

 加密入门-对称密码简介(Java实现)_第8张图片

 

​ DES作为安全性最弱的算法已经在实际运用面临淘汰,而3DES依然在已有的软硬件中返回余热。不过3DES效率太低,在更多的领域被安全和效率兼具的AES所替代。

​ 在实际开发中,对称加密算法这部分的运用大致如下图所示:

加密入门-对称密码简介(Java实现)_第9张图片

 

​ 当然,这只是一个非常简易的模型。我们学习到这,加密也就入门,更多的知识有待于大家进一步发掘!

你可能感兴趣的:(java)