国密起步6:GmSSL3使用SM4自定义格式加解密C++版

初级代码游戏的专栏介绍与文章目录-CSDN博客

我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。

这些代码大部分以Linux为目标但部分代码是纯C++的,可以在任何平台上使用。

github源码指引的指引-CSDN博客


这是将之前写的AES的版本改为SM4,代码变化不大。对应的,也有一个同样的C#版本(国密起步7:BouncyCastle使用SM4自定义格式加解密C#版-CSDN博客)。

这个自定格式是:一字节版本,多个16字节加密块(其中第一个块前8字节是明文长度,后8字节未用)。

一、GmSSL的SM4和AES的区别

sm4只支持128位密钥,其余从编程角度看没什么区别。

CBC、padding这些都是一样的。

因为OpenSSL的CBC有问题,所以我自己写了CBC,也没有使用padding,直接在密文中放了明文长度,因为旧代码已经如此,所以新代码还是这样写的(并非库有什么问题)。

二、源码

cpp 复制代码
//myGmSSL.h 文件编码:UTF-8无签名

#pragma once

#include <iostream>
#include <iomanip>
#include <stdio.h>
#include <stdlib.h>
#include <gmssl/sm4.h>
#include <gmssl/rand.h>
#include <vector>
#include "Buffer.h"
#include "mimetype.h"
using namespace std;

namespace ns_my_std
{
	class CMyGmSSL
	{
	private:
		static unsigned char getVer() { return 1; }

		static void show_buf(char const* title, unsigned char const* buf, int len)
		{
			cout << title << " ";
			for (int i = 0; i < len; ++i)
			{
				cout << hex << setw(2) << setfill('0') << (unsigned int)buf[i] << " ";
			}
			cout << endl;
		}
		//需要一个从用户密码生成密钥的函数
	public:
		class IV
		{
		private:
			unsigned char iv[SM4_BLOCK_SIZE];
		public:
			IV()
			{
				memset(iv, 0, SM4_BLOCK_SIZE);
			}
			//执行异或
			static void XOR(unsigned char const* iv, unsigned char* data)
			{
				//show_buf("IV  ", iv, SM4_BLOCK_SIZE);
				//show_buf("DATA", data, SM4_BLOCK_SIZE);
				for (int i = 0; i < SM4_BLOCK_SIZE; ++i)
				{
					data[i] ^= iv[i];
				}
				//show_buf("DATA", data, SM4_BLOCK_SIZE);
			}
			int size()const
			{
				return SM4_BLOCK_SIZE;
			}
			//用随机数设置
			void Create()
			{
				time_t t = time(NULL);
				srand(t);
				for (int i = 0; i < SM4_BLOCK_SIZE; i += sizeof(int))
				{
					int a = rand();
					memcpy(iv + i, &a, sizeof(int));
				}
			}
			void Set(unsigned char const* a)
			{
				memcpy(iv, a, SM4_BLOCK_SIZE);
			}
			//注意,会修改内容
			unsigned char* Get()
			{
				//show_buf("IV ", iv, SM4_BLOCK_SIZE);
				return iv;
			}
		};
		//所以自行实现CBC
		static void my_SM4_cbc_encrypt(const unsigned char* in, unsigned char* out, size_t length, const SM4_KEY* key, unsigned char* ivec, bool isEnc)
		{
			for (int i = 0; i < (int)length; i += SM4_BLOCK_SIZE)
			{
				if (isEnc)
				{
					unsigned char tmpin[SM4_BLOCK_SIZE];
					memcpy(tmpin, in + i, SM4_BLOCK_SIZE);
					IV::XOR(ivec, tmpin); thelog << endi;
					sm4_encrypt(key, tmpin, out + i); thelog << endi;
					memcpy(ivec, out + i, SM4_BLOCK_SIZE);
				}
				else
				{
					unsigned char tmpiv[SM4_BLOCK_SIZE];
					memcpy(tmpiv, in + i, SM4_BLOCK_SIZE);
					sm4_encrypt(key, in + i, out + i);
					IV::XOR(ivec, out + i);
					memcpy(ivec, tmpiv, SM4_BLOCK_SIZE);
				}
			}
		}
		static int my_sm4_encrypt(unsigned char const* userpasswd, int userpasswd_len, vector<unsigned char> const& in_plain, vector<unsigned char>& out_ciphertext, IV& iv)
		{
			out_ciphertext.clear();

			unsigned char userkey[SM4_KEY_SIZE];
			memset((void*)userkey, '\0', SM4_KEY_SIZE);
			memcpy(userkey, userpasswd, (userpasswd_len > SM4_KEY_SIZE ? SM4_KEY_SIZE : userpasswd_len));
			show_buf("key ", userkey, SM4_KEY_SIZE);

			SM4_KEY key;
			sm4_set_encrypt_key(&key, userkey);

			int len = 0;
			/*循环加密,每次只能加密SM4_BLOCK_SIZE长度的数据*/
			out_ciphertext.reserve(in_plain.size() + SM4_BLOCK_SIZE);
			while (len < (int)in_plain.size())
			{
				if (0 == len)
				{//第一个块是明文长度
					out_ciphertext.resize(out_ciphertext.size() + SM4_BLOCK_SIZE);
					unsigned char tmp[SM4_BLOCK_SIZE];
					memset((void*)tmp, '\0', SM4_BLOCK_SIZE);
					uint64_t tmp_len = in_plain.size();
					memcpy(tmp, &tmp_len, sizeof(uint64_t));
					//show_buf("明文长度加密前 ", tmp, SM4_BLOCK_SIZE);
					my_SM4_cbc_encrypt(tmp, &out_ciphertext[out_ciphertext.size() - SM4_BLOCK_SIZE], SM4_BLOCK_SIZE, &key, iv.Get(), true);
					//show_buf("明文长度加密后", &out_ciphertext[out_ciphertext.size() - SM4_BLOCK_SIZE], SM4_BLOCK_SIZE);
				}
				out_ciphertext.resize(out_ciphertext.size() + SM4_BLOCK_SIZE);
				if (in_plain.size() - len < SM4_BLOCK_SIZE)
				{
					unsigned char tmp[SM4_BLOCK_SIZE];
					memset((void*)tmp, '\0', SM4_BLOCK_SIZE);
					memcpy(tmp, &in_plain[len], in_plain.size() - len);
					my_SM4_cbc_encrypt(tmp, &out_ciphertext[out_ciphertext.size() - SM4_BLOCK_SIZE], SM4_BLOCK_SIZE, &key, iv.Get(), true);
				}
				else
				{
					my_SM4_cbc_encrypt(&in_plain[len], &out_ciphertext[out_ciphertext.size() - SM4_BLOCK_SIZE], SM4_BLOCK_SIZE, &key, iv.Get(), true);
				}
				len += SM4_BLOCK_SIZE;
			}

			return 0;
		}
		static int my_sm4_decrypt(unsigned char const* userpasswd, int userpasswd_len, vector<unsigned char> const& in_ciphertext, vector<unsigned char>& out_plain, IV& iv)
		{
			out_plain.clear();

			unsigned char userkey[SM4_KEY_SIZE];
			memset((void*)userkey, '\0', SM4_KEY_SIZE);
			memcpy(userkey, userpasswd, (userpasswd_len > SM4_KEY_SIZE ? SM4_KEY_SIZE : userpasswd_len));

			SM4_KEY key;
			sm4_set_decrypt_key(&key, userkey);

			int len = 0;
			/*循环解密*/
			out_plain.reserve(in_ciphertext.size());
			uint64_t out_len = 0;//原始长度,放在第一个加密块
			while (len < (int)in_ciphertext.size())
			{
				if (0 == len)
				{//第一个块是明文长度
					unsigned char tmp[SM4_BLOCK_SIZE];
					//show_buf("明文长度解密前", &in_ciphertext[len], SM4_BLOCK_SIZE);
					my_SM4_cbc_encrypt(&in_ciphertext[len], tmp, SM4_BLOCK_SIZE, &key, iv.Get(), false);
					//show_buf("明文长度解密后", tmp, SM4_BLOCK_SIZE);
					memcpy(&out_len, tmp, sizeof(uint64_t));
					//thelog << "明文长度应该是 " << out_len << endi;
					len += SM4_BLOCK_SIZE;
				}
				out_plain.resize(out_plain.size() + SM4_BLOCK_SIZE);
				my_SM4_cbc_encrypt(&in_ciphertext[len], &out_plain[out_plain.size() - SM4_BLOCK_SIZE], SM4_BLOCK_SIZE, &key, iv.Get(), false);
				len += SM4_BLOCK_SIZE;
			}

			//恢复原始长度
			if ((uint64_t)out_plain.size() > out_len)out_plain.resize(out_len);

			return 0;
		}
		//保护数据,用密码加密并做格式转换
		static bool protect_encode(string const& _passwd, string const& _input, string& _output)
		{
			CUnsignedBuffer passwd;
			CUnsignedBuffer input;
			CUnsignedBuffer output;
			passwd.SetData(_passwd.c_str(), _passwd.size());
			input.SetData(_input.c_str(), _input.size());
			if (protect_encode(passwd, input, output))
			{
				_output = (char*)output.data();
				return true;
			}
			return false;
		}
		static bool protect_encode(CUnsignedBuffer const& passwd, CUnsignedBuffer const& input, CUnsignedBuffer& output)
		{
			output.setSize(0);
			IV iv;
			iv.Create();
			//show_buf("IV ", iv.Get(), iv.size());

			CUnsignedBuffer tmp;
			unsigned char ver = getVer();
			tmp.AddData(&ver, 1);//第一个字节是版本
			tmp.AddData(iv.Get(), iv.size());//然后是IV,必须在加密之前保存,加密之后会改变

			//加密
			vector<unsigned char> in_plain;
			in_plain.resize(input.size());
			memcpy(&in_plain[0], input.data(), input.size());
			vector<unsigned char> out_ciphertext;
			my_sm4_encrypt(passwd.data(), passwd.size(), in_plain, out_ciphertext, iv);
			//thelog << out_ciphertext.size() << endi;

			//添加加密后数据
			tmp.AddData(&out_ciphertext[0], out_ciphertext.size());
			//thelog << tmp.size() << endi;

			output.reserve(tmp.size() * 4 / 3 + 4 + 1);//三字节转为4字节,编码函数在最后还会加上一个字符串结束符
			//thelog << output.capacity() << " " << output.size() << endi;
			int n = CBase64::Base64Enc(output.lockBuffer(), tmp.data(), tmp.size());
			output.releaseBuffer();
			if (n > (int)output.capacity())thelog << "长度不足" << ende;
			output.setSize(n);
			//thelog << output.size() << " [" << output.data() << "]" << endi;

			return true;
		}
		//保护数据,用密码加密并做格式转换
		static bool protect_decode(string const& _passwd, string const& _input, string& _output)
		{
			CUnsignedBuffer passwd;
			CUnsignedBuffer input;
			CUnsignedBuffer output;
			passwd.SetData(_passwd.c_str(), _passwd.size());
			input.SetData(_input.c_str(), _input.size());
			if (protect_decode(passwd, input, output))
			{
				_output = (char*)output.data();
				return true;
			}
			return false;
		}
		static bool protect_decode(CUnsignedBuffer const& passwd, CUnsignedBuffer const& input, CUnsignedBuffer& output)
		{
			output.setSize(0);

			CUnsignedBuffer tmp;
			//这里导致了奇怪的内存错误,实际并不需要这么长
			tmp.reserve(input.size() + 100);//实际需要的是4转3,解码函数最后会加上一个字符串结束符
			//thelog << input.size() << " " << tmp.capacity() << " " << tmp.size() << endi;
			int n = CBase64::Base64Dec((char*)tmp.lockBuffer(), (char*)input.data(), input.size());
			tmp.releaseBuffer();
			if (n<0 || n >(int)tmp.capacity())thelog << "长度不足" << ende;
			tmp.setSize(n);

			unsigned char ver = getVer();
			if (tmp.data()[0] != ver)
			{
				thelog << "加密版本错误" << ende;
				return false;
			}
			else
			{
				//thelog << "加密版本 " << (int)tmp.data()[0]<<" " << (int)ver << ende;
			}

			IV iv;
			iv.Set(tmp.data() + 1);

			vector<unsigned char> in_plain;
			in_plain.resize(tmp.size() - 1 - iv.size());
			memcpy(&in_plain[0], tmp.data() + 1 + iv.size(), tmp.size() - 1 - iv.size());
			//thelog << tmp.size() << " " << in_plain.size() << endi;
			vector<unsigned char> out_ciphertext;
			my_sm4_decrypt(passwd.data(), passwd.size(), in_plain, out_ciphertext, iv);

			output.AddData(&out_ciphertext[0], out_ciphertext.size());

			return true;
		}

		static bool protect_encode_test()
		{
			if (true)
			{
				try
				{
					char const* plaintext = "1234567890";
					CUnsignedBuffer pass;
					CUnsignedBuffer in;
					CUnsignedBuffer out;
					pass.SetData("123");
					in.SetData(plaintext);
					thelog << in.data() << endi;
					protect_encode(pass, in, out);
					thelog << out.size() << " [" << out.data() << "]" << endi;
					CUnsignedBuffer out2;
					if (!protect_decode(pass, out, out2))thelog << "解码失败" << ende;
					thelog << out2.data() << endi;
					if (0 == strcmp(plaintext, (char*)out2.data()))
					{
						thelog << "匹配成功" << endi;
					}
					else
					{
						thelog << "匹配失败" << ende;
					}
					string plain_str = "abcdefg";
					string passwd_str = "123";
					string x;
					string y;
					thelog << plain_str << endi;
					protect_encode(passwd_str, plain_str, x);
					thelog << x << endi;
					protect_decode(passwd_str, x, y);
					thelog << y << endi;
					if (y == plain_str)
					{
						thelog << "匹配成功" << endi;
					}
					else
					{
						thelog << "匹配失败" << ende;
					}
				}
				catch (...)
				{
					thelog << "异常发生" << ende;
				}
			}

			return true;
		}
	};
}

这个类是自带测试的,执行"static bool protect_encode_test()"即可测试。

测试代码在othertest目录下。(我似乎是把原来使用GmSSL的AES的代码直接替换掉了,原来的代码在这个文章里面:国密起步1:GmSSL3安装和使用AES-CSDN博客

三、运行结果

cpp 复制代码
[09-12 16:14:48][应用][信息][CommandSet.h            : 236(doCommandSet)][  0.00]
命令表
命令 组           名称          命令 说明
---- ------------ ------------- ---- --------
a    未命名命令组 TestGmSSL     a    国密测试
3    未命名命令组 TestGmSSL_sm3 3    国密测试
4    未命名命令组 TestGmSSL_sm4 4    国密测试
---- ------------ ------------- ---- --------


请选择命令:(--group选择命令组 b=break)(q=exit default=a):
[09-12 16:14:48][应用][信息] 用户输入的是:a
[09-12 16:14:48][应用][信息][myGmSSL.h               : 300(protect_encode_test)][  0.00]1234567890
key  31 32 33 00 00 00 00 00 00 00 00 00 00 00 00 00
[09-12 16:14:48][应用][信息][myGmSSL.h               :  89(my_SM4_cbc_encrypt)][  0.00]
[09-12 16:14:48][应用][信息][myGmSSL.h               :  90(my_SM4_cbc_encrypt)][  0.00]
[09-12 16:14:48][应用][信息][myGmSSL.h               :  89(my_SM4_cbc_encrypt)][  0.00]
[09-12 16:14:48][应用][信息][myGmSSL.h               :  90(my_SM4_cbc_encrypt)][  0.00]
[09-12 16:14:48][应用][信息][myGmSSL.h               : 302(protect_encode_test)][  0.00]68 [AbnISD14qytKFbiaK1JIlxgvLWHPxWkmTAigBfWGZKWdL0eGIdPMYkBZ/W2Bv7n9wA==]
[09-12 16:14:48][应用][信息][myGmSSL.h               : 305(protect_encode_test)][  0.00]1234567890
[09-12 16:14:48][应用][信息][myGmSSL.h               : 308(protect_encode_test)][  0.00]匹配成功
[09-12 16:14:48][应用][信息][myGmSSL.h               : 318(protect_encode_test)][  0.00]abcdefg
key  31 32 33 00 00 00 00 00 00 00 00 00 00 00 00 00
[09-12 16:14:48][应用][信息][myGmSSL.h               :  89(my_SM4_cbc_encrypt)][  0.00]
[09-12 16:14:48][应用][信息][myGmSSL.h               :  90(my_SM4_cbc_encrypt)][  0.00]
[09-12 16:14:48][应用][信息][myGmSSL.h               :  89(my_SM4_cbc_encrypt)][  0.00]
[09-12 16:14:48][应用][信息][myGmSSL.h               :  90(my_SM4_cbc_encrypt)][  0.00]
[09-12 16:14:48][应用][信息][myGmSSL.h               : 320(protect_encode_test)][  0.00]AbnISD14qytKFbiaK1JIlxj1ADRRpQ9RlkcP/F9R1c1NEvTFDjTbuHKQ80ErIQo5Vw==
[09-12 16:14:48][应用][信息][myGmSSL.h               : 322(protect_encode_test)][  0.00]abcdefg
[09-12 16:14:48][应用][信息][myGmSSL.h               : 325(protect_encode_test)][  0.00]匹配成功
[09-12 16:14:48][应用][信息][CommandSet.h            : 129(doCommand)][  0.00]
命令 TestGmSSL 执行完毕 返回码 0

(这里是文档结束)

相关推荐
月夕花晨3742 分钟前
C++学习笔记(30)
c++·笔记·学习
蠢蠢的打码5 分钟前
8584 循环队列的基本操作
数据结构·c++·算法·链表·图论
不是编程家12 分钟前
C++ 第三讲:内存管理
java·开发语言·c++
jianglq21 分钟前
C++高性能线性代数库Armadillo入门
c++·线性代数
Lenyiin2 小时前
《 C++ 修炼全景指南:十 》自平衡的艺术:深入了解 AVL 树的核心原理与实现
数据结构·c++·stl
程序猿练习生3 小时前
C++速通LeetCode中等第5题-无重复字符的最长字串
开发语言·c++·leetcode
无名之逆3 小时前
云原生(Cloud Native)
开发语言·c++·算法·云原生·面试·职场和发展·大学期末
好蛊3 小时前
第 2 课 春晓——cout 语句
c++·算法
景小雨6 小时前
【数据结构与算法】排序算法之快速排序(简)
c++·算法·排序算法·快速排序
鸽嗷高.6 小时前
C++伟大发明--模版
开发语言·c++