对称加密加密原理和开发场景解析

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 加密是自古以来人们都在不断使用的技术,目的是为了隐藏信息,只是随着时代在不断的变化,加密也在不断的更新。从古代的藏宝图对藏宝地点进行隐藏。到二战时候,破译敌方电台,都是属于加密和破解的过程。进入21世纪后,加密在互联网时代也有了新的加密方法。也创造了密码学这个学科。目前在加密的场景下,通常分为:可逆加密和不可逆加密。而在可逆加密场景里又分为:对称加密和非对称加密。本次主要讨论集中在可逆加密上。可逆加密顾名思义就是在对明文进行加密后生成密文,能够通过解密把密文再还原成明文。数据加密一般主要解决三个问题:可信问题(非对称加密可解决),防篡改问题(不可逆加密解决),防窃听问题...

摘要

加密是自古以来人们都在不断使用的技术,目的是为了隐藏信息,只是随着时代在不断的变化,加密也在不断的更新。从古代的藏宝图对藏宝地点进行隐藏。到二战时候,破译敌方电台,都是属于加密和破解的过程。进入21世纪后,加密在互联网时代也有了新的加密方法。也创造了密码学这个学科。目前在加密的场景下,通常分为:可逆加密和不可逆加密。而在可逆加密场景里又分为:对称加密和非对称加密。本次主要讨论集中在可逆加密上。可逆加密顾名思义就是在对明文进行加密后生成密文,能够通过解密把密文再还原成明文。数据加密一般主要解决三个问题:可信问题(非对称加密可解决),防篡改问题(不可逆加密解决),防窃听问题(对称加密可解决,非对称部分可解决)。

对称加密

对称加密的原理就如字面意思所说,加密和解密的密钥是同一个。打个比喻就是:两个人开同一把锁,不分身份都可以用同一把钥匙打开锁。对称加密解决的最主要问题就是:防窃听问题。即在信息传输过程中,不能被第三方获取到信息的明文内容。

  • 优点:
  1. 加解密使用同一个密钥,比较方便。
  2. 加解密的速度快,适合大数据加密。
  • 缺点:
  • 密钥是信息接收和发送双方都在保存。对于密钥保存要求非常高。出现泄密的话,无法确定密钥泄露方。

对称加密算法简介

主流算法及优缺点

目前主流的对称加密算法有:DES,3DES,AES以及中国国密的SM4算法。

算法 优点 缺点
DES(Data Encrypt Standard) 数据标准加密:速度比较快,适合大数据量的数据加密。 速度快,但是安全性不够高
3DES 基于DES,对一块数据用三个不同的密钥进行三次加密,强度更高。 安全性低于AES
AES(Advanced Encryption Standard) 高级加密标准:是下一代加密算法标准,速度快,安全级别更高。可以使用128位,192位,256位的密钥。 -
SM4 中国自主设计,安全性同AES保持一致。由于自主设计,安全可控。 目前应用范围比较小。

AES算法

以AES加密为例来解释对称加密算法:
AES使用的密钥可以是:128位,192位和256位。不同长度的密钥带来的只有复杂度的区分。具体的加密过程分为:轮密钥加,字节代换,行位移,列混合。这四个操作全部操作一遍,为一轮加密。

  1. 轮密钥加:是将128位轮密钥Ki同状态矩阵中的数据进行逐位异或操作。
  2. 字节代换:AES的字符代换其实就是一个简单的查表操作,AES定义了一个S盒和一个逆S盒。
  3. 行位移:就是一个简单的左循环移位操作。
  4. 列混合:是通过矩阵相乘来实现的,经过移位后的状态矩阵与固定的矩阵相乘,得到混淆后的状态矩阵。

其中AES-128位会进行10轮的加密操作。每轮都包含这4个操作(第一轮和第十轮会稍有不同)。在进行循环加密结束后,生成的密文就可以进行传输使用了。这4个操作都是可逆操作,也就保证了解密的可行性。在解密的过程就是对这密文进行逆操作的过程。

轮密钥加

  • 轮密钥加

在这个步骤进行操作的时候,输入的内容是两个:明文和子密钥K[0](可以把k[0]当做密钥本身),这两个都是128bit的大小。两个输入内容按照矩阵排列,分别进行异或操作。其中明文使用P矩阵来表示,子密钥矩阵使用K矩阵。在进行异或操作后生成新的矩阵结果。
image.png

//这里对实际代码进行了一部分简化,方便理解。
void AddRoundKey(unsigned char(*P)[4], unsigned char(*K)[4])
{
    for (int i = 0; i < 4; i++)
    {
        for (int j = 0; j < 4; j++)
        {
            P[i][j] ^= K[i][j];
        }
    }
}
//假设P矩阵如下,每个矩阵元素8bit, 0-255的范围.(无符号)
[2, @, 1, 0]
[a, 4, ^, 1]
[4, -, 1, )]
[9, a, c, .]
//K[0]矩阵如下
[%, *, a, 2]
[=, *, +, )]
[<, 2, #, 1]
[", *, d, ;]
则最后的加密结果矩阵是(16进制表示,因为有些字符是不可见字符):
[0x17, 0x6a, 0x50, 0x2]
[0x5c, 0x1e, 0x75, 0x18]
[0x8, 0x1f, 0x12, 0x18]
[0x1b, 0x4b, 0x7, 0x15]

字节代换

字节代换的操作比较简单,就是有一个S盒矩阵,将输入的字节,根据S盒矩阵,代换为另一个字节。这里的S盒矩阵是通过某种计算方法生成的,S盒矩阵大小为256,16行*16列。在映射的时候,把每个数据8bit的前4bit映射为行(4bit能表示的数字范围为0-15,刚好16),后4bit数据映射到列。进行数据代换。在解密的时候,有一个逆S盒,操作是一样的,叫做逆字节代换。

假设S盒如下:
image.png
逆S盒如下:
image.png
在上一轮的轮密钥加中我们得到矩阵:

[0x17, 0x6a, 0x50, 0x2]
[0x5c, 0x1e, 0x75, 0x18]
[0x8, 0x1f, 0x12, 0x18]
[0x1b, 0x4b, 0x7, 0x15]

我们先对矩阵第一个数据 0x17进行字节代换操作,根据S盒矩阵。如下图,得到0xF0
image.png
而得到的0xF0在逆S盒中,进行解密的逆字节代换可以发现又得到了0x17。
image.png

上一步的矩阵,逐个进行字节代换,得到最终的矩阵:

//S盒
const unsigned char S_Table[16][16] =
{
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16
};

//字节代换
int replaceSTable(unsigned char (*P)[4])
{
    for (int i = 0; i < 4; i++)
    {
        for (int j = 0; j < 4; ++j)
        {
            P[i][j] = S_Table[P[i][j] >> 4][P[i][j] & 0x0F];
        }
    }
}
得到结果矩阵如下:
[0xF0, 0x02, 0x53, 0x77]
[0x4A, 0x72, 0x9D, 0xAD]
[0x30, 0xC0, 0xC9, 0xAD]
[0xAF, 0xB3, 0xC5, 0x59]

行位移

行位移是非常简单的操作,就是在一个4*4的矩阵上,对矩阵的行元素进行一次向左移动操作。而在AES中为何要加入这个如此简单的操作呢? 是因为这是为了下一步的列混合做准备,对矩阵的元素进行一次矩阵乘法,只要对矩阵的元素进行一次行位移,那么就会影响当前矩阵的状态,在下一步的列混合的时候,就会得到完全不一致的结果。达到一个雪崩效应的变化。
上一步骤得到的矩阵结果是:

[0xF0, 0x02, 0x53, 0x77] //不变
[0x4A, 0x72, 0x9D, 0xAD] //向左位移一个
[0x30, 0xC0, 0xC9, 0xAD] //向左位移两个
[0xAF, 0xB3, 0xC5, 0x59] //向左位移三个

void shiftRows(unsigned char *P[4]){
    unsigned char * tmp = (unsigned char *) malloc(sizeof(unsigned char) * 4);
    for (int i = 0 ; i < 4; i++) {
        for (int j = 0; j < i; j++) {
            tmp[j] = P[i][j];
        }
        for (int j = 0; j < 4 - i; j++) {
            P[i][j] = P[i][j+i];
        }
        for (int j = 4 - i; j < 4; j++) {
            P[i][j] = tmp[j+i-4];
        }
    }
    free(tmp);
}
//得到的结果数组为:
[0xf0 0x2 0x53 0x77]
[0x72 0x9d 0xad 0x4a]
[0xc9 0xad 0x30 0xc0]
[0x59 0xaf 0xb3 0xc5]

列混合

列混合是在这个加密逻辑中,比较复杂的操作,是通过将上一步行位移之后的结果,与一个给定的矩阵进行矩阵乘法。不过这个乘法,是在扩展域的乘法和加法。

//列混淆左乘矩阵
const unsigned char MixArray[4][4] =
{
0x02, 0x03, 0x01, 0x01,
0x01, 0x02, 0x03, 0x01,
0x01, 0x01, 0x02, 0x03,
0x03, 0x01, 0x01, 0x02
};

void MixColum(unsigned char(*PlainArray)[4])
{
    //定义变量
    unsigned char ArrayTemp[4][4];
    //初始化变量
    memcpy(ArrayTemp, PlainArray, 16);
    //矩阵乘法 4*4
    for (int i = 0; i < 4; i++)
    {
        for (int j = 0; j < 4; j++)
        {
            PlainArray[i][j] =
            MixArray[i][0] * ArrayTemp[0][j] +
            MixArray[i][1] * ArrayTemp[1][j] +
            MixArray[i][2] * ArrayTemp[2][j] +
            MixArray[i][3] * ArrayTemp[3][j];
        }
    }
}

在扩展域内的加法等于异或运算,而在扩展域内的乘法就是伽罗瓦域乘法。对于伽罗瓦域乘法有兴趣的伽罗瓦域(有限域)
所以需要对这个矩阵乘法,进行伽罗瓦域乘法计算替换。这里不展开这个计算方法。对于逆列混合的时候,不是对矩阵做除法,而是使用逆矩阵,进行正向的乘法计算即可。

对称加密的开发场景应用

在实际的开发场景中,AES加密是用的最多的,第一是因为安全性是有公认的保障的,安全级别足够。第二是加密的速度比较快,作为数据加密算法合适。目前HTTPS的数据传输就是使用的AES加密。

使用场景

一般对称加密的主要应用场景是业务数据信息传输,防止消息被窃听。

  1. 对安全有极高要求的通讯软件。例如:军事领域,安全领域。
  2. 行业安全通信标准。例如:https。

可以简单概括为:只要是在网络中进行传输的数据,如果有防窃听的诉求,都需要对称加密的接入。

使用步骤

  1. 双方互换AES密钥。
  2. 密钥进行明文AES加密。
  3. 使用对方的AES密钥进行解密。

示例代码


import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

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

public class AESCryptoUtil {

    /** AES密钥位数 */
    public static int SECRET_KEY_LENGTH = 128;

    /**
     * 生成AES密钥
     * 
     * @return
     * @throws NoSuchAlgorithmException
     * @throws IOException
     */
    public static byte[] getAutoCreateAESKey() throws NoSuchAlgorithmException {
        KeyGenerator kg = KeyGenerator.getInstance("AES");
        kg.init(128);//要生成多少位,只需要修改这里即可128, 192或256  
        SecretKey sk = kg.generateKey();
        byte[] skBuffer = sk.getEncoded();
        return skBuffer;
    }

    /**
     * 使用AES对称算法进行加密
     * 
     * @param secretKey
     * @param text
     * @return
     * @throws NoSuchPaddingException 
     * @throws NoSuchAlgorithmException 
     * @throws InvalidKeyException 
     * @throws UnsupportedEncodingException 
     * @throws BadPaddingException 
     * @throws IllegalBlockSizeException 
     */
    public static byte[] getAESEncode(byte[] secretKey,
                                      String text) throws NoSuchAlgorithmException,
                                                   NoSuchPaddingException, InvalidKeyException,
                                                   IllegalBlockSizeException, BadPaddingException,
                                                   UnsupportedEncodingException {
        SecretKeySpec sKeySpec = new SecretKeySpec(secretKey, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, sKeySpec);
        byte[] result = cipher.doFinal(text.getBytes("UTF-8"));
        return result;
    }

    /**
     * AES对称算法 解密
     * 
     * @param secretKey
     * @param text
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchPaddingException
     * @throws InvalidKeyException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws UnsupportedEncodingException
     */
    public static String getAESDecode(byte[] secretKey,
                                      byte[] text) throws NoSuchAlgorithmException,
                                                   NoSuchPaddingException, InvalidKeyException,
                                                   IllegalBlockSizeException, BadPaddingException,
                                                   UnsupportedEncodingException {
        SecretKeySpec sKeySpec = new SecretKeySpec(secretKey, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.DECRYPT_MODE, sKeySpec);
        byte[] result = cipher.doFinal(text);
        return new String(result, "UTF-8");
    }

}

运算效率测试

AES加密

对长度500字节的内容进行加密1000次操作,耗时383毫秒。数据大小约为500KB。

public static void main(String[] args) {
        String content
            =
            "1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvw";
        String key = "1234567890abcdef";
        Long timer = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++) {
            encrypt(content, key);
        }
        System.out.println("time:" + (System.currentTimeMillis() - timer));
    }

image.png

AES解密

对生成的密文进行1000次解密,总共耗时344毫秒,大小同样为500KB。与加密性能基本一致。因为从上面的AES加解密步骤可以看到,其正向加密和逆向解密的复杂度是基本一模一样的。所以加解密的耗时基本一致,也在预料之中。

public static void main(String[] args) {
        String content
            =
            "1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvw";
        String key = "1234567890abcdef";
        Long timer = System.currentTimeMillis();
        String res = encrypt(content, key);

        for (int i = 0; i < 1000; i++) {
            decrypt(res, key);
        }
        System.out.println("time:" + (System.currentTimeMillis() - timer));
    }

image.png

交付场景思考

目前对称加密在交付项目上,可以对很多场景进行数据加密。例如:聊天沟通场景,用户A和用户B之间进行消息发送,文件发送,图片发送等等。都需要保证安全的话,就需要对消息进行加密传输。来保证就算被人中间窃取信息,也无法被获取信息。
但是这里有一个比较重要的问题是需要进行:密钥交换。如果保证在协商秘钥期间不会中间人给攻击,就涉及到了比较复杂的密钥交换环节。有感兴趣的可以看:DH密钥交换

目录
相关文章
|
3天前
|
存储 算法 Java
解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用
在Java中,Set接口以其独特的“无重复”特性脱颖而出。本文通过解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用。
14 3
|
15天前
|
前端开发 Java 应用服务中间件
21张图解析Tomcat运行原理与架构全貌
【10月更文挑战第2天】本文通过21张图详细解析了Tomcat的运行原理与架构。Tomcat作为Java Web开发中最流行的Web服务器之一,其架构设计精妙。文章首先介绍了Tomcat的基本组件:Connector(连接器)负责网络通信,Container(容器)处理业务逻辑。连接器内部包括EndPoint、Processor和Adapter等组件,分别处理通信、协议解析和请求封装。容器采用多级结构(Engine、Host、Context、Wrapper),并通过Mapper组件进行请求路由。文章还探讨了Tomcat的生命周期管理、启动与停止机制,并通过源码分析展示了请求处理流程。
|
10天前
|
NoSQL Java Redis
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
这篇文章介绍了如何使用Spring Boot整合Apache Shiro框架进行后端开发,包括认证和授权流程,并使用Redis存储Token以及MD5加密用户密码。
16 0
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
|
12天前
|
开发框架 缓存 前端开发
electron-builder 解析:你了解其背后的构建原理吗?
本文首发于微信公众号“前端徐徐”,详细解析了 electron-builder 的工作原理。electron-builder 是一个专为整合前端项目与 Electron 应用的打包工具,负责管理依赖、生成配置文件及多平台构建。文章介绍了前端项目的构建流程、配置信息收集、依赖处理、asar 打包、附加资源准备、Electron 打包、代码签名、资源压缩、卸载程序生成、安装程序生成及最终安装包输出等环节。通过剖析 electron-builder 的原理,帮助开发者更好地理解和掌握跨端桌面应用的构建流程。
35 2
|
5天前
|
前端开发 JavaScript UED
axios取消请求CancelToken的原理解析及用法示例
axios取消请求CancelToken的原理解析及用法示例
13 0
|
8天前
|
存储 缓存 数据处理
深度解析:Hologres分布式存储引擎设计原理及其优化策略
【10月更文挑战第9天】在大数据时代,数据的规模和复杂性不断增加,这对数据库系统提出了更高的要求。传统的单机数据库难以应对海量数据处理的需求,而分布式数据库通过水平扩展提供了更好的解决方案。阿里云推出的Hologres是一个实时交互式分析服务,它结合了OLAP(在线分析处理)与OLTP(在线事务处理)的优势,能够在大规模数据集上提供低延迟的数据查询能力。本文将深入探讨Hologres分布式存储引擎的设计原理,并介绍一些关键的优化策略。
37 0
|
13天前
|
SQL 分布式计算 大数据
大数据-97 Spark 集群 SparkSQL 原理详细解析 Broadcast Shuffle SQL解析过程(一)
大数据-97 Spark 集群 SparkSQL 原理详细解析 Broadcast Shuffle SQL解析过程(一)
29 0
|
13天前
|
SQL 分布式计算 算法
大数据-97 Spark 集群 SparkSQL 原理详细解析 Broadcast Shuffle SQL解析过程(二)
大数据-97 Spark 集群 SparkSQL 原理详细解析 Broadcast Shuffle SQL解析过程(二)
58 0
|
10天前
|
SQL 安全 算法
网络安全的盾牌与剑:深入理解网络漏洞、加密技术及安全意识
【10月更文挑战第7天】在数字化时代的浪潮中,网络安全成了保护个人隐私和企业资产的关键防线。本文旨在揭示网络安全的重要性,并从网络漏洞识别、加密技术的运用到培养良好的安全意识等方面提供深入浅出的知识分享。通过实例分析,我们将探索如何有效地防范网络攻击,保护信息安全。
|
8天前
|
存储 安全 5G

推荐镜像

更多