【现代密码学】基于国密体系SM2、SM3、SM4构造签密算法(Java实现、源码下载、系统流程图)
1、主要内容
(1)基于 SM2 加密、签名算法,结合 SM3 哈希算法生成 SM4 的加密密钥
(2)使用 SM4 加密生成密文
(3)根据密文及 SM2 恢复解密密钥,使用 SM4 恢复明文并进行验证
2、设计目的与需求分析
充分利用我们所学的 SM2,SM3 和 SM4 算法,构造一个具 有签名特性的加解密算法。
将多种加密算法结合对于了解密码算法和构造密码算法有着 非同寻常的意义,同时将多种密钥算法相互结合的思想早在古典密码中的维吉尼亚密码中有体现。但此次基于 SM2,SM3 和 SM4 的密码算法的结合具有创新意义。
3、实验环境
win10、IntelliJ IDEA 2020
4、系统介绍
4.1 系统组成
采用了 SM2 的加解密和签名技术,SM3 的杂凑算法,MD5 的杂 凑算法,SM4 的分组密码加解密技术,对通信双方构造一个签密算法。
4.2.所用密码学知识
SM2 算法:SM2 椭圆曲线公钥密码算法是我国自主设计的公钥密码算法,包括 SM2-1 椭圆曲线数字签名算法,SM2-2 椭圆曲线密钥交换协议,SM2-3 椭圆曲线公钥加密 算法,分别用于实现数字签名密钥协商和数据加密等功能。SM2 算法与 RSA 算法不同的 是,SM2 算法是基于椭圆曲线上点群离散对数难题,相对于 RSA 算法,256 位的 SM2 密 码强度已经比 2048 位的 RSA 密码强度要高。 SM2 原理:SM2 椭圆曲线公钥密码算法:我国自主知识产权的商用密码算法,是 ECC (Elliptic Curve Cryptosystem)算法的一种,基于椭圆曲线离散对数问题,计算复杂度 是指数级,求解难度较大,同等安全程度要求下,椭圆曲线密码较其他公钥所需密钥长度小很多。
ECC 算法描述:
1、用户 A 选定一条适合加密的椭圆曲线 Ep(a,b)(如:y2=x3+ax+b),并取椭圆曲线上 一点,作为基点 G。
2、用户 A 选择一个私有密钥 k,并生成公开密钥(公钥 PB)K=kG。
3、用户 A 将 Ep(a,b)和点(公钥)K,G 传给用户 B。
4、用户 B 接到信息后 ,将待传输的明文(M)编码到 Ep(a,b)上一点 M,并产生 一个随机整数 r(r<n)。
加密开始
5、用户 B 计算点 C1=M+rK;C2=rG。
6、用户 B 将 C1、C2 传给用户 A。
7、用户 A 接到信息后,计算 C1-kC2,结果就是点 M。
8、因为 C1-kC2=M+rK-k(rG)=M+rK-r(kG)=M 再对点 M 进行解码就可以得到明文。
密码学中,描述一条 Fp 上的椭圆曲线,常用到六个参量:
T=(p,a,b,G,n,h)。 (p 、a 、b 用来确定一条椭圆曲线,G 为基点,n 为点 G 的阶,h 是椭圆曲线上所有点的个数 m 与 n 相除的整数部分)
这几个参量取值的选择,直接影响了加密的安全性。参量值一般要求满足以下几个条件:
1、p 当然越大越安全,但越大,计算速度会变慢,200 位左右可以满足一般安全要求;
2、 p≠n×h;
3、pt≠1 (mod n),1≤t<20;
4、4a3+27b2≠0 (mod p);
5、n 为素数;
6、h≤4。
SM3 算法:SM3 杂凑算法是我国自主设计的密码杂凑算法,适用于商用密码应用 中的数字签名和验证消息认证码的生成与验证以及随机数的生成,可满足多种密码应用 的安全需求。为了保证杂凑算法的安全性,其产生的杂凑值的长度不应太短,例如 MD5 输出 128 比特杂凑值,输出长度太短,影响其安全性 SHA-1 算法的输出长度为 160 比特, SM3 算法的输出长度为 256 比特,因此 SM3 算法的安全性要高于 MD5 算法和 SHA-1 算法。
SM4 算法:SM4 分组密码算法是我国自主设计的分组对称密码算法,用于实现数 据的加密/解密运算,以保证数据和信息的机密性。要保证一个对称密码算法的安全性 的基本条件是其具备足够的密钥长度,SM4 算法与 AES 算法具有相同的密钥长度分组长 度 128 比特,因此在安全性上高于 3DES 算法
4.3 系统代码实现
主要采用了 JAVA 语言对 SM2,SM3,SM4,MD5 算法进行了部分改写。 对于已经开源的代码我们可以之间从互联网上进行获取。但对于代码的兼容性进行了 部分的修改,同时在主函数上进行了一次签密算法的应用
5、系统流程图分析
SM2:
(1)SM2的签名过程如图
(2)SM2的加密如图:
(3)SM2解密过程
(4)SM3加密过程
(5)SM4加密过程
(6)SM4解密过程
系统总流程图
6、关键代码演示
话不多说,上代码
package org.pzone.crypto;
import org.bouncycastle.math.ec.ECPoint;
import java.math.BigInteger;
/**
* SM2密钥对Bean
* @author Potato
*
*/
public class SM2KeyPair {
private final ECPoint publicKey;
private final BigInteger privateKey;
SM2KeyPair(ECPoint publicKey, BigInteger privateKey) {
this.publicKey = publicKey;
this.privateKey = privateKey;
}
public ECPoint getPublicKey() {
return publicKey;
}
public BigInteger getPrivateKey() {
return privateKey;
}
}
package org.pzone.crypto;
import java.math.BigInteger;
/**
* 国密办文件中推荐的椭圆曲线相关参数
* @author Potato
*
*/
public class Params {
private static BigInteger n = new BigInteger(
"FFFFFFFE" + "FFFFFFFF" + "FFFFFFFF" + "FFFFFFFF" + "7203DF6B" + "21C6052B" + "53BBF409" + "39D54123", 16);
private static BigInteger p = new BigInteger(
"FFFFFFFE" + "FFFFFFFF" + "FFFFFFFF" + "FFFFFFFF" + "FFFFFFFF" + "00000000" + "FFFFFFFF" + "FFFFFFFF", 16);
private static BigInteger a = new BigInteger(
"FFFFFFFE" + "FFFFFFFF" + "FFFFFFFF" + "FFFFFFFF" + "FFFFFFFF" + "00000000" + "FFFFFFFF" + "FFFFFFFC", 16);
private static BigInteger b = new BigInteger(
"28E9FA9E" + "9D9F5E34" + "4D5A9E4B" + "CF6509A7" + "F39789F5" + "15AB8F92" + "DDBCBD41" + "4D940E93", 16);
private static BigInteger gx = new BigInteger(
"32C4AE2C" + "1F198119" + "5F990446" + "6A39C994" + "8FE30BBF" + "F2660BE1" + "715A4589" + "334C74C7", 16);
private static BigInteger gy = new BigInteger(
"BC3736A2" + "F4F6779C" + "59BDCEE3" + "6B692153" + "D0A9877C" + "C62A4740" + "02DF32E5" + "2139F0A0", 16);
}
package org.pzone.crypto;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import java.io.*;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.Arrays;
/**
* SM2公钥加密算法实现 包括 -签名,验签 -密钥交换 -公钥加密,私钥解密
*
* @author Potato
*
*/
public class SM2 {
private static BigInteger n = new BigInteger(
"FFFFFFFE" + "FFFFFFFF" + "FFFFFFFF" + "FFFFFFFF" + "7203DF6B" + "21C6052B" + "53BBF409" + "39D54123", 16);
private static BigInteger p = new BigInteger(
"FFFFFFFE" + "FFFFFFFF" + "FFFFFFFF" + "FFFFFFFF" + "FFFFFFFF" + "00000000" + "FFFFFFFF" + "FFFFFFFF", 16);
private static BigInteger a = new BigInteger(
"FFFFFFFE" + "FFFFFFFF" + "FFFFFFFF" + "FFFFFFFF" + "FFFFFFFF" + "00000000" + "FFFFFFFF" + "FFFFFFFC", 16);
private static BigInteger b = new BigInteger(
"28E9FA9E" + "9D9F5E34" + "4D5A9E4B" + "CF6509A7" + "F39789F5" + "15AB8F92" + "DDBCBD41" + "4D940E93", 16);
private static BigInteger gx = new BigInteger(
"32C4AE2C" + "1F198119" + "5F990446" + "6A39C994" + "8FE30BBF" + "F2660BE1" + "715A4589" + "334C74C7", 16);
private static BigInteger gy = new BigInteger(
"BC3736A2" + "F4F6779C" + "59BDCEE3" + "6B692153" + "D0A9877C" + "C62A4740" + "02DF32E5" + "2139F0A0", 16);
private static ECDomainParameters ecc_bc_spec;
private static int w = (int) Math.ceil(n.bitLength() * 1.0 / 2) - 1;
private static BigInteger _2w = new BigInteger("2").pow(w);
private static final int DIGEST_LENGTH = 32;
private static SecureRandom random = new SecureRandom();
private static ECCurve.Fp curve;
private static ECPoint G;
private boolean debug = false;
public boolean isDebug() {
return debug;
}
public void setDebug(boolean debug) {
this.debug = debug;
}
/**
* 以16进制打印字节数组
*
* @param
*/
public static void printHexString(byte[] b) {
for (int i = 0; i < b.length; i++) {
String hex = Integer.toHexString(b[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
System.out.print(hex.toUpperCase());
}
System.out.println();
}
/**
* 数组转16进制
*
* @param
*/
public static String transHexString(byte[] b) {
String Str="";
for (int i = 0; i < b.length; i++) {
String hex = Integer.toHexString(b[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
Str=Str+hex;
}
return Str;
}
/**
* 随机数生成器
*
* @param max
* @return
*/
private static BigInteger random(BigInteger max) {
BigInteger r = new BigInteger(256, random);
// int count = 1;
while (r.compareTo(max) >= 0) {
r = new BigInteger(128, random);
// count++;
}
// System.out.println("count: " + count);
return r;
}
/**
* 判断字节数组是否全0
*
* @param buffer
* @return
*/
private boolean allZero(byte[] buffer) {
for (int i = 0; i < buffer.length; i++) {
if (buffer[i] != 0)
return false;
}
return true;
}
/**
* 公钥加密
*
* @param input
* 加密原文
* @param publicKey
* 公钥
* @return
*/
public byte[] encrypt(String input, ECPoint publicKey) {
byte[] inputBuffer = input.getBytes();
if (debug)
printHexString(inputBuffer);
byte[] C1Buffer;
ECPoint kpb;
byte[] t;
do {
/* 1 产生随机数k,k属于[1, n-1] */
BigInteger k = random(n);
if (debug) {
System.out.print("k: ");
printHexString(k.toByteArray());
}
/* 2 计算椭圆曲线点C1 = [k]G = (x1, y1) */
ECPoint C1 = G.multiply(k);
C1Buffer = C1.getEncoded(false);
if (debug) {
System.out.print("C1: ");
printHexString(C1Buffer);
}
/*
* 3 计算椭圆曲线点 S = [h]Pb
*/
BigInteger h = ecc_bc_spec.getH();
if (h != null) {
ECPoint S = publicKey.multiply(h);
if (S.isInfinity())
throw new IllegalStateException();
}
/* 4 计算 [k]PB = (x2, y2) */
kpb = publicKey.multiply(k).normalize();
/* 5 计算 t = KDF(x2||y2, klen) */
byte[] kpbBytes = kpb.getEncoded(false);
t = KDF(kpbBytes, inputBuffer.length);
// DerivationFunction kdf = new KDF1BytesGenerator(new
// ShortenedDigest(new SHA256Digest(), DIGEST_LENGTH));
//
// t = new byte[inputBuffer.length];
// kdf.init(new ISO18033KDFParameters(kpbBytes));
// kdf.generateBytes(t, 0, t.length);
} while (allZero(t));
/* 6 计算C2=M^t */
byte[] C2 = new byte[inputBuffer.length];
for (int i = 0; i < inputBuffer.length; i++) {
C2[i] = (byte) (inputBuffer[i] ^ t[i]);
}
/* 7 计算C3 = Hash(x2 || M || y2) */
byte[] C3 = sm3hash(kpb.getXCoord().toBigInteger().toByteArray(), inputBuffer,
kpb.getYCoord().toBigInteger().toByteArray());
/* 8 输出密文 C=C1 || C2 || C3 */
byte[] encryptResult = new byte[C1Buffer.length + C2.length + C3.length];
System.arraycopy(C1Buffer, 0, encryptResult, 0, C1Buffer.length);
System.arraycopy(C2, 0, encryptResult, C1Buffer.length, C2.length);
System.arraycopy(C3, 0, encryptResult, C1Buffer.length + C2.length, C3.length);
if (debug) {
System.out.print("密文: ");
printHexString(encryptResult);
}
return encryptResult;
}
/**
* 私钥解密
*
* @param encryptData
* 密文数据字节数组
* @param privateKey
* 解密私钥
* @return
*/
public String decrypt(byte[] encryptData, BigInteger privateKey) {
if (debug)
System.out.println("encryptData length: " + encryptData.length);
byte[] C1Byte = new byte[65];
System.arraycopy(encryptData, 0, C1Byte, 0, C1Byte.length);
ECPoint C1 = curve.decodePoint(C1Byte).normalize();
/*
* 计算椭圆曲线点 S = [h]C1 是否为无穷点
*/
BigInteger h = ecc_bc_spec.getH();
if (h != null) {
ECPoint S = C1.multiply(h);
if (S.isInfinity())
throw new IllegalStateException();
}
/* 计算[dB]C1 = (x2, y2) */
ECPoint dBC1 = C1.multiply(privateKey).normalize();
/* 计算t = KDF(x2 || y2, klen) */
byte[] dBC1Bytes = dBC1.getEncoded(false);
int klen = encryptData.length - 65 - DIGEST_LENGTH;
byte[] t = KDF(dBC1Bytes, klen);
// DerivationFunction kdf = new KDF1BytesGenerator(new
// ShortenedDigest(new SHA256Digest(), DIGEST_LENGTH));
// if (debug)
// System.out.println("klen = " + klen);
// kdf.init(new ISO18033KDFParameters(dBC1Bytes));
// kdf.generateBytes(t, 0, t.length);
if (allZero(t)) {
System.err.println("all zero");
throw new IllegalStateException();
}
/* 5 计算M'=C2^t */
byte[] M =