芯片AES加密密钥生成工具

前言:
  嵌入式单片机开发,为了防止别人将芯片内的代码读取出来通过反编译手段拿到源码,常用的手段是对芯片和烧录文件进行加密。大部分的芯片厂商都会提供一个加密烧录和配置文件的工具。这个工具一般需要你填写一定长度的密钥,如果密钥填写得太有规律比如:123456,或项目名+版本号等,这样的密钥对公司来说极度不安全,如果被对手知道别人很可能直接破解出你的源码,然后抄一下你的板就和你一模一样出货了,别人省去了开发成本何乐不为呢?所以本文介绍一种通过AES加密算法将明文转换为密文的工具制作。AES加密算法目前没有破解办法,据我所知微信小程序就用到了这种加密算法。

1 模块构成

  制作这样的工具只需要三个模块即可完成,AES加密算法模块、芯片加密模块、GUI模块。本文使用Pathon3语言开发。

2 AES模块

  AES加密是一种常见的对称加密算法,具有较高的安全性。之所以选择用Python3来做这个工具,就是因为其丰富的现成模块支持,没必要重复造轮子,Windows下我们直接导入pycryptodome模块。由于是对称加密其基本形式是明文+密钥=密文密文+密钥=明文
  虽然AES算法不用我们实现,但是其原理和参数需要有一定的了解。实现一次AES运算需要输入密钥、明文、模式、偏移量、字符集类型、数据块、填充内容。输出即是密文。

2.1 输入参数:

  • 密钥:因为是对称加密算法,所以加密和解密都需要使用这个密钥参与运算。
  • 明文:需要被加密的字符串,建议限制用ASCII字符。
  • 模式:AES有很多种加密模式,最常用的是ECB和CBC,如果是ECB模式则不需要设置偏移量,如果是CBC模式则需要设置偏移量。
  • 偏移量:偏移量会被带入到加密运算过程中影响加密结果,具体看下算法理论部分就明白了。
  • 字符集类型:输入输出都是文本,需要指明文本类型。
  • 数据块:算法运算原理要求输入的明文和密钥需要是128、192、256Bits的整数倍,也即16、24、32字节的整数倍。
  • 填充内容:上面提到了数据块要求明文和密钥是三种字节的整数倍,如果不是则需要填充一些字符使其成为整数倍这样才能进行AES算法的运算。所以这个参数即是定义填充的字符。

2.2 模块内函数:

  • _init_(self, Key, Model, IV, Encode, DataBlockLen, SupplementChar) 构造函数,输入AES的参数。
  • DataBlock(self, OriginalKey, DataBlockLen, SupplementChar) 对数据(指明文和密钥)进行块化,填充使其为16、24、32字节的整数倍。
  • AESEncrypt(self, OriginalText) 加密函数,将明文转换为密文。
  • AESDecrypt(self, DecryptedText) 解密函数,将密文转换为明文。我们的工具用于加密,所以这个函数实际没被调用,但我还是将其写出了。
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
'''
@file AESCrypt.py
@brief AES加密算法类
@details 创建该对象进行AES加密
@author Calm
@data 2020-03-13
@version 1.0.0
@copyright Calm
'''

from Crypto.Cipher import AES # Window下需要安装pycryptodome模块
import base64


class AESCrypt:
'''
@fn __init__(self, Key, Model, IV, Encode, DataBlockLen, SupplementChar)
@brief 带参构造函数,输入AES加密的参数。
@details ECB加密模式无偏移量IV,CBC需要设置IV。需要确保密钥进行了块化。
@param[in] Key 加密密钥。
@param[in] Model 加密模式(ECB、CBC等)。
@param[in] IV 加密偏移量。
@param[in] Encode 字符集类型(gb2312、gbk等)。
@param[in] DataBlockLen 数据块(单位是字节)(密钥和明文长度需是128、192、256Bit的倍数。
@param[in] SupplementChar 对数据块进行填充,使得数据为128、192、或256Bit的倍数。
@return None
@note 无。
@attention 无
'''
def __init__(self, Key, Model, IV, Encode, DataBlockLen, SupplementChar):
self.AESEncode = Encode
self.SupplementChar = SupplementChar
self.AESModel = {'ECB': AES.MODE_ECB, 'CBC': AES.MODE_CBC}[Model]
self.AESDataBlockLen = DataBlockLen
self.AESSupplementKey = self.DataBlock(Key, self.AESDataBlockLen, self.SupplementChar)

if Model == 'ECB':
self.AES = AES.new(self.AESSupplementKey, self.AESModel)
elif Model == 'CBC':
self.AES = AES.new(self.AESSupplementKey, self.AESModel, IV)

return

'''
@fn DataBlock(self, OriginalKey, DataBlockLen, SupplementChar)
@brief 对数据进行块化。
@details 数据块必须是128、192、256Bit的倍数。
@param[in] OriginalKey 原始未块化数据。
@param[in] DataBlockLen 块化后数据的长度。
@param[in] SupplementChar 用于填充的字符。
@return 块化后的数据。
@note 无。
@attention 无
'''
def DataBlock(self, OriginalKey, DataBlockLen, SupplementChar):
SupplementData = OriginalKey.encode(self.AESEncode)

#如果数据不是块长度的整数倍,则对数据进行填充
while len(SupplementData) % DataBlockLen != 0:
SupplementData += SupplementChar.encode('utf-8')

return SupplementData

'''
@fn AESEncrypt(self, OriginalText)
@brief AES加密,将明文转化为密文。
@details 需要确保加密的是块化后的数据。
@param[in] OriginalText 原始未块化明文。
@return 密文
@note 无。
@attention 无
'''
def AESEncrypt(self, OriginalText):
SupplementText = self.DataBlock(OriginalText, self.AESDataBlockLen, self.SupplementChar)
EncryptedText = self.AES.encrypt(SupplementText)

return base64.encodebytes(EncryptedText).decode().strip()

'''
@fn AESDecrypt(self, DecryptedText)
@brief AES解密,将密文转化为明文。
@details 无。
@param[in] DecryptedText 密文。
@return 明文
@note 无。
@attention 无
'''
def AESDecrypt(self, DecryptedText):
OriginalText = base64.decodebytes(DecryptedText.encode(self.AESEncode))
OriginalText = self.AES.decrypt(OriginalText)

return OriginalText .decode(self.AESEncode).strip('\0')

HDSCCrypt模块

  对特定芯片和固件进行加密时设定AES参数。

  • 输入参数:
    • 明文:需要被加密的字符串。
    • 其他参数:其他参数就是上面提到的AES加密需要的参数。
  • 模块内函数:
    • init(self) 构造函数,输入AES加密相关的参数。
    • HDSCEncrypt(self, Plaintext) 针对特定厂商芯片的加密函数,将明文转换为密文。
    • CheckPlaintext(self, Plaintext) 对明问进行格式检查,当含有非ASCII字符或空白时抛出错误码。
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
'''
@file HDSCCrypt.py
@brief 固件加密类
@details 创建该对象进行固件加密
@author Calm
@data 2020-03-13
@version 1.0.0
@copyright Calm
'''

from AESCrypt import *
import re


class HDSCCrypt:
'''
@fn __init__(self)
@brief 无参构造函数。
@details 固件使用AES加密的参数在构造函数里设定。
@return None
@note 无。
@attention 无
'''
def __init__(self):
self.HDSCCryptKey = "123456789"
self.HDSCCryptModel = "ECB"
self.HDSCCryptIV = ""
self.HDSCCryptEncode = "gbk"
self.HDSCCryptDataBlockLen = 16
self.AESCrypt = AESCrypt(self.HDSCCryptKey, self.HDSCCryptModel, self.HDSCCryptIV, self.HDSCCryptEncode, self.HDSCCryptDataBlockLen, "\x00")

return

'''
@fn HDSCEncrypt(self, Plaintext)
@brief 计算烧录固件和芯片加密的密钥。
@details 无。
@param[in] Plaintext 明文。
@return 密文
@note 无。
@attention 无
'''
def HDSCEncrypt(self, Plaintext):
return self.AESCrypt.AESEncrypt(Plaintext)

'''
@fn CheckPlaintext(self, Plaintext)
@brief 对明文输入进行格式检查。
@details 限制为不含空白字符的ASCII码。
@param[in] Plaintext 明文。
@return 检查结果
- 0 正确
- 1 含有空白字符
- 2 含有非ASCII码
- 3 空明文
@note 无。
@attention 无
'''
def CheckPlaintext(self, Plaintext):
Ret = 0

if re.match('.*\s.*', Plaintext) is not None: #包含空白字符
Ret = 1
elif len(Plaintext) != len(Plaintext.encode('utf-8')): #含有非ASCII码
Ret = 2
elif Plaintext == "": #明文为空
Ret = 3
else: #正确明文
Ret = 0

return Ret

CryptGUI模块

  在Windows下构造GUI。这个GUI界面非常简洁,两个编辑框和两个按键,相应的有两个按键的回调函数。

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
'''
@file CryptGUI.py
@brief 固件加密软件GUI类
@details 创建该对象进行固件加密GUI生成
@author Calm
@data 2020-03-13
@version 1.0.0
@copyright Calm
'''

from tkinter import *
import tkinter.messagebox
from HDSCCrypt import *
from IconResourse import Icon
import os


class CryptGUI:
'''
@fn __init__(self, StaticText1, StaticText2, ButtonText1, ButtonText2)
@brief 带参构造函数,参数可设置静态文本和按钮文本。
@details 无。
@param[in] StaticText1 静态文本1。
@param[in] StaticText2 静态文本2。
@param[in] ButtonText1 按钮文本1。
@param[in] ButtonText2 按钮文本2。
@return None
@note 无。
@attention 无
'''
def __init__(self, StaticText1, StaticText2, ButtonText1, ButtonText2):
self.Window = Tk()
self.Str1 = StringVar()
self.Str2 = StringVar()
self.HDSCFirmwareKey = HDSCCrypt()

# 对Label进行表格式布局
Label(self.Window, text=StaticText1).grid(row=0, column=0)
Label(self.Window, text=StaticText2).grid(row=1, column=0)

# 对编辑框进行表格式布局
Edit1 = Entry(self.Window, textvariable=self.Str1, width=32)
Edit2 = Entry(self.Window, textvariable=self.Str2, state='readonly', width=32)
Edit1.grid(row=0, column=1, padx=10, pady=5)
Edit2.grid(row=1, column=1, padx=10, pady=5)

Button(self.Window, text=ButtonText1, width=10, command=self.CallBackButton1).grid(row=3, column=0, sticky=W,
padx=10,
pady=5)
Button(self.Window, text=ButtonText2, width=10, command=self.CallBackButton2).grid(row=3, column=1, sticky=E,
padx=10,
pady=5)

ScreenWidth, ScreenHeigh = self.Window.maxsize()
CurWidth = self.Window.winfo_reqwidth()
CurHeight = self.Window.winfo_reqheight()
self.Window.geometry("+{}+{}".format(ScreenWidth // 2 - CurWidth, ScreenHeigh // 2 - CurHeight))
#设置窗口是否可变长、宽,True:可变,False:不可变
self.Window.resizable(width=False, height=False)

#图标资源保存在IconResourse.py里,目的是保证生成的exe不依赖其他文件
tmp = open("tmp.icon", "wb+")
tmp.write(base64.b64decode(Icon)) #临时图标文件
tmp.close()
#修改图标
self.Window.iconbitmap("tmp.icon")
os.remove("tmp.icon") #删除临时图标文件
#修改窗口标题
self.Window.title("HDSCCrypt V1.0.0")

return

'''
@fn CallBackButton1(self)
@brief 按钮1回调函数。
@details 读取编辑框1内容进行加密后显示在编辑框2。会对明文格式进行检查。
@return None
@note 无。
@attention 无
'''
def CallBackButton1(self):
Plaintext = self.Str1.get()
self.Str2.set("")

CheckResult = self.HDSCFirmwareKey.CheckPlaintext(Plaintext )

if CheckResult == 1:
tkinter.messagebox.showinfo('Error', '明文不符合规范\n\n\n错误原因:含有空白字符。')
elif CheckResult == 2:
tkinter.messagebox.showinfo('Error', '明文不符合规范\n\n\n错误原因:限制只能使用ASCII字符。')
elif CheckResult == 3:
pass
else:
Ciphertext = self.HDSCFirmwareKey.HDSCEncrypt(Plaintext)

if len(Ciphertext) > 16:
Ciphertext = Ciphertext[0:16]

self.Str2.set(Ciphertext)

return

'''
@fn CallBackButton2(self)
@brief 按钮2回调函数。
@details 弹出关于对话框,显示描述信息。
@return None
@note 无。
@attention 无
'''
def CallBackButton2(self):
tkinter.messagebox.showinfo("关于", "使用说明:该软件用于单片机进行芯片和烧录文件加密时,将明文信息转换为密文。如果发现Bug请联系作者。\n\n作者:Calm\n发布日期:2020-03-13\n发布版本:V1.0.0\n版权:Calm")

return

'''
@fn CryptGUILoopRun(self)
@brief 开始对GUI时间监听。
@details 无。
@return None
@note 无。
@attention 无
'''
def CryptGUILoopRun(self):
mainloop()

return

最后是入口函数模块

  模块封装好后调用就非常简单了。

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
'''
@file main.py
@brief 入口文件
@details 无
@author Calm
@data 2020-03-13
@version 1.0.0
@copyright Calm
'''

from CryptGUI import *

'''
@fn main()
@brief 入口函数
@details 无
@return None
@note 无。
@attention 无
'''

def main():
#创建加密GUI窗口对象
HDSCCryptGUI = CryptGUI("明文: ", "密文: ", "加密", "关于")
HDSCCryptGUI.CryptGUILoopRun()

return


if __name__ == "__main__":
main()