首页
社区
课程
招聘
[原创]加解密之国密算法SM4初识
发表于: 2023-5-23 22:42 7130

[原创]加解密之国密算法SM4初识

2023-5-23 22:42
7130

加解密之国密算法SM4

国密算法是中国国家密码管理局发布的一系列密码算法标准,包括SM1、SM2、SM3、SM4、SM9等。

  1. SM1: 这是一个分组密码算法,也就是块加密算法。这是中国自己的算法,但并未公开。它主要用于商用密码芯片、密码设备和密码产品等,目前主要在金融领域应用。

  2. SM2: 这是一个基于椭圆曲线密码的公钥密码算法,包含了数字签名、密钥交换和公钥加密等多种算法。SM2的安全强度和效率均优于RSA算法和Diffie-Hellman算法。

  3. SM3: 这是一个密码哈希函数,类似于MD5和SHA-1。SM3对于长度小于2^64的消息,经过填充和迭代压缩操作,可以产生一个256比特的消息摘要输出。由于其优秀的安全性和效率,SM3被广泛应用在数字签名、消息认证码生成等诸多场景。

  4. SM4: 这是一个分组密码算法,也就是块加密算法,曾被称为SMS4。它是一个对称加密算法,密钥长度和分组长度都是128位。SM4被广泛用于无线局域网标准中的WAPI(无线局域网身份认证和密钥管理协议)。

  5. SM9: 这是一种基于身份的密码体制,其中包含了身份基于签名、密钥协商和公钥加密等多种算法。SM9的优点是无需在通信双方之间预先分配和存储密钥,只需要使用对方的身份信息即可进行加密和签名。

这些算法被广泛应用于网络安全、数据加密、电子签名等领域,并在中国的多个行业和领域得到推广和应用。

学习目录

1. sm4算法流程

 

2. SM4算法实现

 

3. 其他杂识基础

sm4算法流程

SM4是中国自主研发的分组密码算法,采用分组密码的思路,每次处理一组固定长度的数据块。SM4用于无线局域网的国家标准,已经应用于WAPI(无线局域网身份验证与隐私协议)。

 

以下是SM4算法的主要流程:

  1. 密钥扩展(Key Expansion): 输入128位密钥,通过一系列的处理(如非线性变换、线性变换和循环左移等),生成32个轮密钥。

  2. 初始变换(Initial Transformation): 输入128位明文,进行初始置换,置换后的结果作为轮函数的输入。

  3. 轮函数(Round Function): 轮函数是SM4的核心部分,每个轮函数由非线性变换(S盒置换)、线性变换和轮密钥加变换组成。轮函数迭代32次,每次的输入是上一轮的输出,并添加一个轮密钥。

  4. 逆初始变换(Inverse Initial Transformation): 最后一轮的输出经过逆初始变换,得到128位的密文。

需要注意的是,SM4有两种模式:加密模式和解密模式。加密模式按照上述流程执行,解密模式基本相同,只是轮密钥的顺序相反。

 

此外,SM4也支持不同的加密模式,如ECB(电子密码本模式)、CBC(密码块链模式)、CFB(密码反馈模式)和OFB(输出反馈模式),在不同的应用场景下,可以选择适合的加密模式。

 

接下来,我们写一个cbc模式的sm4加解密算法。

SM4算法实现

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
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
#include <jni.h>
#include <string>
 
#include <iostream>
#include <cstdint>
#include <cstdint>
#include <iostream>
#include <iomanip>
#include <string>
 
#include <android/log.h>
 
#define TAG "yyfsm" // 可自定义标签
 
//// 在代码中使用 LOG 宏打印消息
//__android_log_print(ANDROID_LOG_DEBUG, TAG, "Your message");
//
//// 打印带有格式化字符串的消息
//__android_log_print(ANDROID_LOG_DEBUG, TAG, "Formatted message: %s", yourStringVariable);
 
#include <iostream>
#include <iomanip>
#include <string>
#include <cstdint>
#include <cstring>
#include <vector>
 
const uint8_t SM4_SBOX_TABLE[16][16] = {
        {0xd6, 0x90, 0xe9, 0xfe, 0xcc, 0xe1, 0x3d, 0xb7, 0x16, 0xb6, 0x14, 0xc2, 0x28, 0xfb, 0x2c, 0x05},
        {0x2b, 0x67, 0x9a, 0x76, 0x2a, 0xbe, 0x04, 0xc3, 0xaa, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99},
        {0x9c, 0x42, 0x50, 0xf4, 0x91, 0xef, 0x98, 0x7a, 0x33, 0x54, 0x0b, 0x43, 0xed, 0xcf, 0xac, 0x62},
        {0xe4, 0xb3, 0x1c, 0xa9, 0xc9, 0x08, 0xe8, 0x95, 0x80, 0xdf, 0x94, 0xfa, 0x75, 0x8f, 0x3f, 0xa6},
        {0x47, 0x07, 0xa7, 0xfc, 0xf3, 0x73, 0x17, 0xba, 0x83, 0x59, 0x3c, 0x19, 0xe6, 0x85, 0x4f, 0xa8},
        {0x68, 0x6b, 0x81, 0xb2, 0x71, 0x64, 0xda, 0x8b, 0xf8, 0xeb, 0x0f, 0x4b, 0x70, 0x56, 0x9d, 0x35},
        {0x1e, 0x24, 0x0e, 0x5e, 0x63, 0x58, 0xd1, 0xa2, 0x25, 0x22, 0x7c, 0x3b, 0x01, 0x21, 0x78, 0x87},
        {0xd4, 0x00, 0x46, 0x57, 0x9f, 0xd3, 0x27, 0x52, 0x4c, 0x36, 0x02, 0xe7, 0xa0, 0xc4, 0xc8, 0x9e},
        {0xea, 0xbf, 0x8a, 0xd2, 0x40, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f},
        {0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f},
        {0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f},
        {0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f},
        {0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f},
        {0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f},
        {0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f},
};
 
const uint32_t SM4_ROUND_CONSTANTS[32] = {
        0x000000C0, 0x000000D8, 0x000000F0, 0x00000108, 0x00000120, 0x00000138, 0x00000150, 0x00000168,
        0x00000180, 0x00000198, 0x000001B0, 0x000001C8, 0x000001E0, 0x000001F8, 0x00000210, 0x00000228,
        0x00000240, 0x00000258, 0x00000270, 0x00000288, 0x000002A0, 0x000002B8, 0x000002D0, 0x000002E8,
        0x00000300, 0x00000318, 0x00000330, 0x00000348, 0x00000360, 0x00000378, 0x00000390, 0x000003A8
};
 
// 32-bit left rotation
#define SM4_LEFT_ROTATE(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
 
// S-box substitution
#define SM4_SBOX(t)                                                             \
    (SM4_SBOX_TABLE[((t) >> 4) & 0x0F][(t) & 0x0F])
 
// Linear transformation L
#define SM4_LTRANSFORM(t)                                                       \
    ((t) ^ SM4_LEFT_ROTATE((t), 2) ^ SM4_LEFT_ROTATE((t), 10) ^                 \
    SM4_LEFT_ROTATE((t), 18) ^ SM4_LEFT_ROTATE((t), 24))
 
// Key expansion
void sm4_key_schedule(const uint8_t* key, uint32_t* rk) {
    uint32_t k[4];
    memcpy(k, key, sizeof(uint32_t) * 4);
 
    for (int i = 0; i < 32; ++i) {
        uint32_t temp = k[0] ^ SM4_SBOX(k[1] ^ k[2] ^ k[3] ^ SM4_ROUND_CONSTANTS[i]);
        k[0] = k[1];
        k[1] = k[2];
        k[2] = k[3];
        k[3] = temp;
        rk[i] = temp;
    }
}
 
// Encryption
//void sm4_encrypt(const uint32_t* rk, const uint8_t* input, uint8_t* output) {
//    uint32_t x[36];
//    memcpy(x, input, sizeof(uint32_t) * 4);
//
//    for (int i = 0; i < 32; ++i) {
//        uint32_t t = x[i + 1] ^ x[i + 2] ^ x[i + 3] ^ rk[i];
//        uint32_t y = SM4_SBOX(t);
//        x[i + 4] = x[i] ^ SM4_LTRANSFORM(y);
//    }
//
//    memcpy(output, x + 32, sizeof(uint32_t) * 4);
//}
void sm4_encrypt(const uint32_t* rk, const uint8_t* input, const uint8_t* iv, uint8_t* output) {
    uint32_t x[36];
    for (int i = 0; i < 4; i++) {
        x[i] = (input[i * 4] << 24) | (input[i * 4 + 1] << 16) | (input[i * 4 + 2] << 8) | input[i * 4 + 3];
    }
 
    // XOR with IV
    for (int i = 0; i < 4; ++i) {
        uint32_t iv_tmp = (iv[i * 4] << 24) | (iv[i * 4 + 1] << 16) | (iv[i * 4 + 2] << 8) | iv[i * 4 + 3];
        x[i] ^= iv_tmp;
    }
 
    for (int i = 0; i < 32; ++i) {
        uint32_t t = x[i + 1] ^ x[i + 2] ^ x[i + 3] ^ rk[i];
        uint32_t y = SM4_SBOX(t);
        x[i + 4] = x[i] ^ SM4_LTRANSFORM(y);
    }
 
    for (int i = 0; i < 4; i++) {
        output[i * 4] = (x[35 - i] >> 24) & 0xFF;
        output[i * 4 + 1] = (x[35 - i] >> 16) & 0xFF;
        output[i * 4 + 2] = (x[35 - i] >> 8) & 0xFF;
        output[i * 4 + 3] = x[35 - i] & 0xFF;
    }
}
 
void sm4_decrypt(const uint32_t* rk, const uint8_t* input, const uint8_t* iv, uint8_t* output) {
    uint32_t x[36];
    for (int i = 0; i < 4; i++) {
        x[i] = (input[i * 4] << 24) | (input[i * 4 + 1] << 16) | (input[i * 4 + 2] << 8) | input[i * 4 + 3];
    }
 
    for (int i = 0; i < 32; ++i) {
        uint32_t t = x[i + 1] ^ x[i + 2] ^ x[i + 3] ^ rk[31 - i];
        uint32_t y = SM4_SBOX(t);
        x[i + 4] = x[i] ^ SM4_LTRANSFORM(y);
    }
 
    // XOR with IV
    for (int i = 0; i < 4; ++i) {
        uint32_t iv_tmp = (iv[i * 4] << 24) | (iv[i * 4 + 1] << 16) | (iv[i * 4 + 2] << 8) | iv[i * 4 + 3];
        x[35 - i] ^= iv_tmp;
    }
 
    for (int i = 0; i < 4; i++) {
        output[i * 4] = (x[35 - i] >> 24) & 0xFF;
        output[i * 4 + 1] = (x[35 - i] >> 16) & 0xFF;
        output[i * 4 + 2] = (x[35 - i] >> 8) & 0xFF;
        output[i * 4 + 3] = x[35 - i] & 0xFF;
    }
}
 
 
 
// Padding
void sm4_pad(uint8_t* data, size_t size) {
    size_t remainder = size % 16;
    size_t paddingSize = (remainder == 0) ? 16 : (16 - remainder);
 
    for (size_t i = 0; i < paddingSize; ++i) {
        data[size + i] = static_cast<uint8_t>(paddingSize);
    }
}
 
// Remove padding
void sm4_unpad(uint8_t* data, size_t size) {
    size_t paddingSize = static_cast<size_t>(data[size - 1]);
    if (paddingSize <= size) {
        for (size_t i = 0; i < paddingSize; ++i) {
            if (data[size - i - 1] != paddingSize) {
                return;
            }
        }
        memset(data + size - paddingSize, 0, paddingSize);
    }
}
 
int main() {
    // Test encryption and decryption
    std::string key = "0123456789ABCDEFFEDCBA9876543210";
    uint8_t value = 0123456701234567;
    const uint8_t *iv = &value;
 
    std::string plaintext = "asd";
    std::vector<uint8_t> encrypted(plaintext.size());
    std::vector<uint8_t> decrypted(plaintext.size());
 
    // Key expansion
    uint32_t rk[32];
    sm4_key_schedule(reinterpret_cast<const uint8_t*>(key.data()), rk);
 
    // Encryption
    sm4_encrypt(rk, reinterpret_cast<const uint8_t*>(plaintext.data()),iv, encrypted.data());
    __android_log_print(ANDROID_LOG_DEBUG, TAG, "明 文:%s",plaintext.data());
    __android_log_print(ANDROID_LOG_DEBUG, TAG, "密 文:%s",encrypted.data());
    // Decryption
    sm4_decrypt(rk, encrypted.data(), iv,decrypted.data());
    __android_log_print(ANDROID_LOG_DEBUG, TAG, "解密明 文:%s",decrypted.data());
 
    return 0;
}
 
 
 
 
 
extern "C" JNIEXPORT jstring JNICALL
Java_com_yyf_sm_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    int a= main();
    std::cout << "Decrypted: " << a << std::endl;
 
    return env->NewStringUTF(hello.c_str());
}


随便在应用市场上找个APP下载,查看是否用了SM4的常量值。

 

其他杂识基础

"与"运算是一种二进制运算符,它接受两个等长的二进制数字,并为每一对相应的位执行"与"运算。在每一对位中,只有当两位都是1时,结果才是1。否则,结果是0。

 

例如,让我们来看看两个8位的二进制数的"与"运算:

1
2
3
4
  10110101
& 01101110
-----------
  00100100

这是如何工作的:

  • 第一位:1 & 0 = 0
  • 第二位:0 & 1 = 0
  • 第三位:1 & 1 = 1
  • 第四位:1 & 0 = 0
  • 第五位:0 & 1 = 0
  • 第六位:1 & 1 = 1
  • 第七位:0 & 0 = 0
  • 第八位:1 & 0 = 0

因此,10110101 & 01101110 = 00100100

异或

异或(XOR)是一种二进制运算,用符号 "^" 表示。其操作规则如下:

 

0 ^ 0 = 0
1 ^ 0 = 1
0 ^ 1 = 1
1 ^ 1 = 0

 

异或运算有以下特性:
任何数与0异或,结果仍然是原来的数。
任何数与自己异或,结果是0。
举个例子,假设我们要进行以下的异或运算:

 

1011 (这是 11 的二进制形式)
^
0111 (这是 7 的二进制形式)


 

1100 (这是 12 的二进制形式)

 

我们按位进行异或运算,也就是说,我们比较每一位,如果两位相同,则结果为0,如果两位不同,则结果为1。这就是为什么我们得到了结果 1100,这是 12 的二进制形式。

 

再举一个更具体的例子,假设我们在编程中进行以下操作(在大多数编程语言中,符号 "^" 表示异或运算):

 

uint32_t a = 0xA3B1BAC6;

 

uint32_t b = 0x56AA3350;

 

a = 0xA3B1BAC6 转换为二进制为: 10100011 10110001 10111010 11000110

 

b = 0x56AA3350 转换为二进制为: 01010110 10101010 00110011 01010000

 

接下来,我们将这两个二进制数进行异或运算,规则是:同一位上,两个数相同得0,两个数不同得1。进行异或运算后的结果如下:

 

11110101 00011110 10001000 10100110

 

uint32_t result = a ^ b; // result 的值现在是 0xF51E88A6

字节与字符

在C++中,std::string是一种序列容器,用于存储和操作字符串。std::string中的每个字符通常代表8位。这是因为std::string通常存储的是ASCII字符,而ASCII字符集是一个8位字符集。

 

在ARM64(也称为AArch64)平台上,C++的基本数据类型一般按以下方式分配字节:

  • char: 1字节 (8位)
  • short: 2字节 (16位)
  • int: 4字节 (32位)
  • long: 8字节 (64位)
  • long long: 8字节 (64位)
  • float: 4字节 (32位)
  • double: 8字节 (64位)
  • long double: 16字节 (128位) — 注意,实际使用的精度可能小于16字节
  • bool: 1字节 (8位)
  • pointer (任何类型的指针): 8字节 (64位)

最好使用sizeof运算符计算大小。

ASCII字符集

ASCII(美国标准信息交换码)是一种字符编码标准,用于电子设备中的字符表示。ASCII使用指定的7位或8位来表示字符,但是通常我们使用的是扩展的版本,也就是8位的ASCII(有时候也叫做ASCII-256),因为8位的二进制数能够表示更多的字符。

 

基本的ASCII集合,也就是标准的ASCII,使用7位二进制数,可以表示128个不同的字符。这包括大小写英文字母(A-Z,a-z),数字(0-9),标点符号(比如,. , ; : ? !等等),一些特殊字符(比如,$ @ # % ^ & *等等),还有一些非打印字符(比如,换行符,退格符等)。

 

扩展的ASCII,或者称为8位ASCII,可以表示256个不同的字符,因为8位二进制数可以有256种不同的组合。扩展的ASCII包含了基本的ASCII集合,另外还增加了一些额外的特殊字符,比如版权符号,注册商标符号,货币符号等。

 

大家如果感兴趣可以关注公众号,日常不定期分享 移动ios安卓相关内容干货不限于开发逆向 图片描述


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2023-6-16 15:18 被涿州飞神编辑 ,原因: 增加二维码
收藏
免费 6
支持
分享
最新回复 (2)
雪    币: 1310
活跃值: (727)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
2
不错的文章,某加固就用了此算法。
2023-6-2 10:19
1
雪    币: 14501
活跃值: (17498)
能力值: ( LV12,RANK:290 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2023-6-2 10:47
1
游客
登录 | 注册 方可回帖
返回
//