数据加解密算法是信息安全领域的重要组成部分,它们用于保护数据的机密性、完整性和真实性。在实际应用中,综合使用数据加解密算法以构建多层次的安全防护体系。例如,HTTPS
协议结合了对称加密(如AES
)用于快速数据传输加密,以及非对称加密(如RSA
)用于安全地交换对称密钥。同时,使用摘要算法确保数据完整性,并通过数字签名验证消息来源的合法性。这种组合方式既解决了数据的机密性问题,又保障了通信的完整性和认证性,是现代网络安全架构的基石。
算法分类
IV
)等,以增加加密的安全性。MAC
的主要目的是提供消息的完整性和认证。它不仅验证数据是否被篡改,还确保接收者能够验证消息确实来自已知的发送者。作用
使用场景
VPN
、SSL/TLS
加密通信。解决的问题
随机数生成算法是指用于生成看似随机数列的一系列算法,这些数列虽然在计算机中被称为“伪随机数”,因为它们实际上是通过确定性的算法计算出来的,但它们在统计特性上足够接近真正的随机性,从而适用于多种应用场景。计算机生成的随机数依赖于初始种子值,一个好的种子来源(如系统时间、硬件噪声等)能够增加随机数序列的不可预测性。
使用场景
OTP
)用于账户安全。SSL/TLS
握手过程中生成随机数。解决的问题
代表性算法
PRNG
,以其周期长和随机性良好而著称。SHA-1
哈希函数的PRNG
,曾被用于生成加密密钥。SHA-1 PRNG
(Pseudorandom Number Generator
)是一种基于SHA-1
哈希算法的伪随机数生成器。它使用 SHA-1
算法对种子值进行哈希,并使用哈希结果生成伪随机数序列。
特点
SHA-1 PRNG
使用SHA-1
哈希算法作为其核心算法,SHA-1
在安全性上已经有一定的破解风险,因此SHA-1 PRNG
也可能存在安全性问题。在对安全性要求较高的场景下,建议使用更安全的算法。SHA-1 PRNG
生成的随机数序列具有很长的周期,通常情况下可以满足大多数应用的需求。但是,由于其基于SHA-1
算法的特性,周期长度可能受到SHA-1
哈希算法的限制。SHA-1 PRNG
的速度较快,生成随机数的效率高,适用于大部分需要高质量随机数的应用场景。示例代码
java@Test
public void testSHA1PRNG() throws NoSuchAlgorithmException {
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
byte[] randomBytes;
for (int i = 0; i < 5; i++) {
randomBytes = new byte[16];
secureRandom.nextBytes(randomBytes);
System.out.println("Random bytes: " + this.byteArrayToHexString(randomBytes));
}
}
private String byteArrayToHexString(byte[] bytes) {
StringBuilder stringBuilder = new StringBuilder();
for (byte b : bytes) {
stringBuilder.append(String.format("%02x", b));
}
return stringBuilder.toString();
}
在上述示例代码中,使用SHA1PRNG
生成随机字节数组,然后将其转换为十六进制字符串后输出,运行并观察控制台输出:
Random bytes: f5b548d8527a02de15492034499ea476 Random bytes: 7009d835283685b8a06b75eeeac2ea49 Random bytes: 9eb62777f5ad603c5c9feb509e7545a6 Random bytes: fb402efe9c121183ec7bee8529da49dd Random bytes: d5060453969d094461d0d8823af13b45
默认情况下,使用SHA1PRNG
算法生成的数据都是随机的,重复执行上述的示例代码,每次生成的数据都是不同的,这是因为生成随机数的种子是变化的,如果对其设置一个固定的种子数据,同一个SecureRandom
对象可以生成不同的随机数据,但是使用相同的种子重新创建SecureRandom
对象后,生成的数据是一致的,即固定种子的随机数生成序列是固定的。
java@Test
public void testSHA1PRNG() throws NoSuchAlgorithmException {
for (int i = 0; i < 3; i++) {
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
byte[] randomBytes = new byte[16];
secureRandom.nextBytes(randomBytes);
System.out.println("Random bytes: " + this.byteArrayToHexString(randomBytes));
}
System.out.println("==================");
for (int i = 0; i < 3; i++) {
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed("jianggujin".getBytes());
byte[] randomBytes = new byte[16];
secureRandom.nextBytes(randomBytes);
System.out.println("Random bytes: " + this.byteArrayToHexString(randomBytes));
}
}
运行调整后的示例代码,观察控制台输出可以看出固定种子后的数据变化。
Random bytes: a1c88f17dec80195a2eeafbbee530a47 Random bytes: a52dec60e560c48bcd7a5127948e531d Random bytes: 52ab81956c4e097969dc4615ed20ad04 ================== Random bytes: e929c5d3c2e8d63ed03c86bf2ec514c5 Random bytes: e929c5d3c2e8d63ed03c86bf2ec514c5 Random bytes: e929c5d3c2e8d63ed03c86bf2ec514c5
信息编码算法是将信息(包括文本、图像、音频、视频等)转换成适合存储、处理、传输的数字形式的过程。编码的目标在于有效地利用有限的资源(如带宽、存储空间)并确保信息的完整性、安全性和高效性。编码不仅仅是简单的二进制转换,还涉及到数据压缩、错误检测与纠正、安全性增强等多个方面。
使用场景
解决的问题
代表性算法
URL
编码是一种将URL
中的特殊字符转换为可安全传输的ASCII
字符串的方法。它将非 ASCII
字符和特殊字符转换成%
后跟两位十六进制数字的形式,以便在URL
中进行传输。
使用场景
URL
中传输包含特殊字符的数据时,需要进行URL
编码以确保传输的安全性和准确性。URL
时,需要对参数值进行URL
编码,以避免解析错误和安全问题。Web
表单提交中,对表单数据进行URL
编码可以确保数据的完整性和正确性。示例代码
javaString str = "蒋固金";
String encoded = URLEncoder.encode(str, "UTF-8");
System.out.println("Encoded URL: " + encoded);
String decoded = URLDecoder.decode(encoded, "UTF-8");
System.out.println("Decoded URL: " + decoded);
gostr := "蒋固金"
encoded := url.QueryEscape(str)
fmt.Println("Encoded URL:", encoded)
decoded, _ := url.QueryUnescape(encoded)
fmt.Println("Decoded URL:", decoded)
运行并观察控制台输出:
Encoded URL: %E8%92%8B%E5%9B%BA%E9%87%91 Decoded URL: 蒋固金
Base32
是一种基于32
个字符的编码方式,用于将二进制数据转换为可打印的ASCII
字符串。它将每5
个比特的二进制数据转换为一个字符,因此每个字符表示32
种可能性,Base32
字符集通常由大写字母A-Z
和数字2-7
组成。
特点
Base32
编码生成的字符串只包含可打印字符,适用于将二进制数据表示为文本格式。Base32
编码可以容忍少量数据损坏或错误,因为每个字符只包含一部分原始数据。Base32
每个字符只能表示5
个比特的数据,因此它的数据密度较低,编码后的字符串长度较长。使用场景:
Base32
编码可用于URL
中传输二进制数据,尤其是在需要避免使用非 ASCII
字符的情况下。Base32
编码可以将二进制数据以文本形式存储在文件或数据库中。Base32
编码常用于计算数据的校验和,如CRC32
校验等。示例代码
javaString str = "jianggujin";
Base32 base32 = new Base32();
String encoded = base32.encodeAsString(str.getBytes(StandardCharsets.UTF_8));
System.out.println("Encoded string: " + encoded);
String decoded = new String(base32.decode(encoded), StandardCharsets.UTF_8);
System.out.println("Decoded string: " + decoded);
注
Base32
使用commons-codec
提供的实现。
xml<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
gostr := "jianggujin"
encoded := base32.StdEncoding.EncodeToString([]byte(str))
fmt.Println("Encoded string:", encoded)
decoded, _ := base32.StdEncoding.DecodeString(encoded)
fmt.Println("Decoded string:", string(decoded))
运行并观察控制台输出:
Encoded string: NJUWC3THM52WU2LO Decoded string: jianggujin
Base62
编码是一种基于62
个字符的编码方式,用于将二进制数据转换为可打印的ASCII
字符串。它将每6
个比特的二进制数据转换为一个字符,因此每个字符表示62
种可能性,Base62
字符集通常由大小写字母A-Z
和数字0-9
组成,共62
个字符。。
特点
Base62
编码生成的字符串长度相对较短,数据密度高,适用于需要短字符串表示较长数据的场景。Base62
编码生成的字符串包含大小写字母和数字,易于人类阅读和理解。Base64
编码。由于Base62
使用了更少的字符,因此在编码相同数量的二进制数据时,理论上可以产生更短的文本字符串。这意味着Base62
的数据密度更高,因为它可以用更少的字符表示更多的信息。使用场景
Base62
编码可用于URL
中传输二进制数据,尤其是在需要短链接的场景下。Base62
编码常用于短链接服务,将长URL
转换为短字符串以便分享和传播。Base62
编码可用于将二进制数据以文本形式存储在文件或数据库中,节省存储空间并提高可读性。示例代码
javaString str = "jianggujin";
String encoded = Base62.encode(str);
System.out.println("Encoded string: " + encoded);
String decoded = Base62.decodeStr(encoded);
System.out.println("Decoded string: " + decoded);
注
Base62
使用hutool
提供的实现。
xml<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.9</version>
</dependency>
gostr := "jianggujin"
encoded := base62.StdEncoding.EncodeToString([]byte(str))
fmt.Println("Encoded string:", encoded)
decoded, _ := base62.StdEncoding.DecodeString(encoded)
fmt.Println("Decoded string:", string(decoded))
Go
语言中未提供base62
实现,可使用如下自定义实现。
gopackage base62
import (
"math"
"reflect"
"strconv"
"unsafe"
)
const encodeStd = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
// StdEncoding is the standard base62 encoding.
var StdEncoding = NewEncoding(encodeStd)
type CorruptInputError int64
func (e CorruptInputError) Error() string {
return "illegal base62 data at input byte " + strconv.FormatInt(int64(e), 10)
}
/*
* Encodings
*/
// An Encoding is a radix 62 encoding/decoding scheme, defined by a 62-character alphabet.
type Encoding struct {
encode [62]byte
decodeMap [256]byte
}
// NewEncoding returns a new padded Encoding defined by the given alphabet,
// which must be a 62-byte string that does not contain the padding character
// or CR / LF ('\r', '\n').
func NewEncoding(encoder string) *Encoding {
if len(encoder) != 62 {
panic("encoding alphabet is not 62-bytes long")
}
for i := 0; i < len(encoder); i++ {
if encoder[i] == '\n' || encoder[i] == '\r' {
panic("encoding alphabet contains newline character")
}
}
e := new(Encoding)
copy(e.encode[:], encoder)
for i := 0; i < len(e.decodeMap); i++ {
e.decodeMap[i] = 0xFF
}
for i := 0; i < len(encoder); i++ {
e.decodeMap[encoder[i]] = byte(i)
}
return e
}
/*
* Encoder
*/
// Encode encodes src using the encoding enc.
func (enc *Encoding) Encode(src []byte) []byte {
if len(src) == 0 {
return nil
}
// enc is a pointer receiver, so the use of enc.encode within the hot
// loop below means a nil check at every operation. Lift that nil check
// outside of the loop to speed up the encoder.
_ = enc.encode
rs := 0
cs := int(math.Ceil(math.Log(256) / math.Log(62) * float64(len(src))))
dst := make([]byte, cs)
for i := range src {
c := 0
v := int(src[i])
for j := cs - 1; j >= 0 && (v != 0 || c < rs); j-- {
v += 256 * int(dst[j])
dst[j] = byte(v % 62)
v /= 62
c++
}
rs = c
}
for i := range dst {
dst[i] = enc.encode[dst[i]]
}
if cs > rs {
return dst[cs-rs:]
}
return dst
}
// EncodeToString returns the base62 encoding of src.
func (enc *Encoding) EncodeToString(src []byte) string {
buf := enc.Encode(src)
return string(buf)
}
/*
* Decoder
*/
// Decode decodes src using the encoding enc.
// If src contains invalid base62 data, it will return the
// number of bytes successfully written and CorruptInputError.
// New line characters (\r and \n) are ignored.
func (enc *Encoding) Decode(src []byte) ([]byte, error) {
if len(src) == 0 {
return nil, nil
}
// Lift the nil check outside of the loop. enc.decodeMap is directly
// used later in this function, to let the compiler know that the
// receiver can't be nil.
_ = enc.decodeMap
rs := 0
cs := int(math.Ceil(math.Log(62) / math.Log(256) * float64(len(src))))
dst := make([]byte, cs)
for i := range src {
if src[i] == '\n' || src[i] == '\r' {
continue
}
c := 0
v := int(enc.decodeMap[src[i]])
if v == 255 {
return nil, CorruptInputError(src[i])
}
for j := cs - 1; j >= 0 && (v != 0 || c < rs); j-- {
v += 62 * int(dst[j])
dst[j] = byte(v % 256)
v /= 256
c++
}
rs = c
}
if cs > rs {
return dst[cs-rs:], nil
}
return dst, nil
}
// DecodeString returns the bytes represented by the base62 string s.
func (enc *Encoding) DecodeString(s string) ([]byte, error) {
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
bh := reflect.SliceHeader{Data: sh.Data, Len: sh.Len, Cap: sh.Len}
return enc.Decode(*(*[]byte)(unsafe.Pointer(&bh)))
}
运行并观察控制台输出:
Encoded string: 2VkxGnG6YAPIP0 Decoded string: jianggujin
Base64
编码是一种将二进制数据转换为可打印ASCII
字符串的编码方式。它将每6
个比特的输入数据转换为一个可打印字符,以便于传输和存储,Base64
字符集通常由大小写字母A-Z
、a-z
、数字0-9
和两个额外的字符(通常是+
和/
)组成,共64
个字符。
特点
Base64
编码生成的字符串只包含可打印字符,适用于将二进制数据表示为文本格式。Base64
每个字符只能表示6
个比特的数据,因此它的数据密度较低,编码后的字符串长度较长。Base64
编码可以容忍少量数据损坏或错误,因为每个字符只包含一部分原始数据。使用场景
Base64
编码常用于在网络上传输二进制数据,如在邮件附件、图像、音频等数据的传输中。Base64
编码可以将二进制数据以文本形式存储在文件或数据库中,方便存储和读取。Base64
编码被用于表示密钥和签名等数据。示例代码
javaString str = "jianggujin";
String encoded = Base64.getEncoder().encodeToString(str.getBytes(StandardCharsets.UTF_8));
System.out.println("Encoded string: " + encoded);
String decoded = new String(Base64.getDecoder().decode(encoded));
System.out.println("Decoded string: " + decoded);
gostr := "jianggujin"
encoded := base64.StdEncoding.EncodeToString([]byte(str))
fmt.Println("Encoded string:", encoded)
decoded, _ := base64.StdEncoding.DecodeString(encoded)
fmt.Println("Decoded string:", string(decoded))
运行并观察控制台输出:
Encoded string: amlhbmdndWppbg== Decoded string: jianggujin
提示
上述示例中使用的是标准的Base64
模式,除了标准的Base64
编码模式外,还有一些常见的变体模式,包括URL
安全模式、MIME
类型模式和无填充模式。这些模式在编码过程中会对字符集、填充方式等进行调整,以适应不同的使用场景。以下是它们之间的区别:
Base64
编码中,使用字符+
和/
分别表示62
和63
,这些字符在URL
中有特殊含义,需要进行转义。因此,URL
安全模式将+
和/
替换为-
和 _
,以避免在URL
中引起歧义。常用于在URL
中传输Base64
编码的数据,以避免URL
转义和解码的问题。MIME
类型模式在标准的Base64
编码基础上,使用字符+
和/
表示 62
和63
,但是会在编码后的字符串末尾添加一个或两个=
作为填充字符。常用于表示 MIME
类型的数据,如邮件附件等。Base64
编码中不使用填充字符=
。这意味着编码后的字符串长度可能不是4
的倍数。在一些特定场景下,需要节省空间或者不需要严格的长度要求时可以使用无填充模式。这些变体模式在编码和解码过程中的基本原理与标准的Base64
编码相同,只是在字符集和填充方式上有所不同,以适应不同的使用场景。
十六进制编码是一种将数据转换为十六进制表示的编码方式。它将数据中的每个字节转换为两位十六进制数字表示,以便于传输和存储。在十六进制编码中,每个字节的高四位和低四位分别转换为对应的十六进制数字,例如,字节值0xAB
被表示为AB
。
特点
使用场景
示例代码
javaprivate final char[] DIGITS_LOWER = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'a', 'b', 'c', 'd', 'e', 'f' };
@Test
public void testHex() throws Exception {
String str = "jianggujin";
String encoded = new String(encodeHex(str.getBytes(StandardCharsets.UTF_8)));
System.out.println("Encoded string: " + encoded);
String decoded = new String(decodeHex(encoded));
System.out.println("Decoded string: " + decoded);
}
private char[] encodeHex(byte[] data) {
int l = data.length;
char[] out = new char[l << 1];
for (int i = 0, j = 0; i < l; i++) {
out[j++] = DIGITS_LOWER[(0xF0 & data[i]) >>> 4];
out[j++] = DIGITS_LOWER[0x0F & data[i]];
}
return out;
}
private byte[] decodeHex(String data) {
int len = data.length();
if ((len & 0x01) != 0) {
throw new IllegalArgumentException("Odd number of characters.");
}
byte[] out = new byte[len >> 1];
for (int i = 0, j = 0; j < len; i++) {
int f = toDigit(data.charAt(j), j) << 4;
j++;
f = f | toDigit(data.charAt(j), j);
j++;
out[i] = (byte) (f & 0xFF);
}
return out;
}
private static int toDigit(char ch, int index) {
final int digit = Character.digit(ch, 16);
if (digit == -1) {
throw new IllegalArgumentException("Illegal hexadecimal character "
+ ch + " at index " + index);
}
return digit;
}
gostr := "jianggujin"
encoded := hex.EncodeToString([]byte(str))
fmt.Println("Encoded string:", encoded)
decoded, _ := hex.DecodeString(encoded)
fmt.Println("Decoded string:", string(decoded))
运行并观察控制台输出:
Encoded string: 6a69616e6767756a696e Decoded string: jianggujin
哈希算法,也称为散列函数或摘要算法,是一种将任意长度的输入数据(消息)映射为固定长度输出值(哈希值或摘要)的数学函数。哈希值通常较小,具有以下特性:输入敏感性(即使是微小的输入变化也会导致哈希值的巨大不同)、不可逆性(从哈希值很难或几乎不可能复原原始输入数据)、确定性(相同输入总是产生相同输出)、高效性(计算速度快)。哈希算法在设计上应尽量避免冲突,即不同的输入产生相同的输出(哈希碰撞),但实际上完全避免碰撞在数学上是不可能的,只能尽量降低其发生的概率。
使用场景
解决的问题
代表性算法
SHA-256
、SHA-512
等,是目前广泛使用的一系列安全哈希算法。SHA-2
的后继者,基于不同的数学基础,旨在提供更长远的安全性。SHA-2
算法。RIPEMD-160
,曾被用在某些加密货币中。MD5
(Message Digest Algorithm 5
)是一种常用的哈希算法,用于产生消息摘要。它将任意长度的消息作为输入,经过一系列操作生成固定长度(128
比特或16
字节)的哈希值。MD5
算法的核心原理是将输入的消息按照一定的规则分成若干个块,并对每个块进行一系列的位操作和模运算,最终生成一个128
比特(16
字节)的哈希值。MD5
算法的设计是为了保证输入消息的微小变化会导致输出哈希值的大幅度变化,从而提高了哈希值的唯一性。
特点
MD5
算法生成的哈希值固定长度为128
比特,无论输入消息的长度如何。MD5
算法是单向哈希算法,不可逆,即无法从哈希值反推出原始消息。MD5
算法在计算哈希值时非常高效,适用于对大量数据进行哈希计算。MD5
算法存在碰撞(两个不同的消息产生相同的哈希值)的概率。使用场景
MD5
常用于校验文件完整性,通过比对文件的MD5
哈希值来验证文件是否被篡改。MD5
可以用于存储用户密码的哈希值,以增加密码的安全性。MD5
可以用于生成消息摘要,用于数字签名和身份验证等场景。示例代码
javaString str = "jianggujin";
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] messageDigest = md.digest(str.getBytes(StandardCharsets.UTF_8));
System.out.println("MD5 hash: " + new String(encodeHex(messageDigest)));
gostr := "jianggujin"
encoded := md5.Sum([]byte(str))
fmt.Println("MD5 hash:", hex.EncodeToString(encoded[:]))
运行并观察控制台输出:
MD5 hash: acab4efdfd3b8efcdec37fe160d7be0e
SHA
(安全哈希算法)是一组密码哈希函数,用于产生消息的哈希值。SHA
算法家族包括了多个版本,如SHA-1
、SHA-256
、SHA-512
等,它们都是在输入数据上执行相似操作但输出长度不同的哈希函数。SHA
算法基于密集型的位操作、逻辑运算和模运算。它将输入数据按照一定的方式分块,然后对每个块进行一系列的位运算和置换,最终产生一个固定长度的哈希值。
特点
SHA
算法生成的哈希值长度不同,但每个版本的输出长度是固定的。SHA
算法是单向哈希函数,不可逆,即无法从哈希值反推出原始消息。SHA
算法在设计上具有很高的碰撞概率,即使输入数据发生微小变化,输出哈希值也会有显著差异。SHA
算法是一种被广泛使用且安全可靠的哈希算法,通常用于数字签名、消息完整性校验等安全领域。使用场景
SHA
算法常用于生成数字签名,用于验证数据的完整性和真实性。SHA
算法可用于存储用户密码的哈希值,增加密码的安全性。SHA
算法可用于消息认证码(HMAC
)的计算,用于验证消息的真实性和完整性。SHA
算法可用于校验文件完整性,如下载文件的哈希值与预期值进行比对。示例代码
javaString str = "jianggujin";
MessageDigest md = MessageDigest.getInstance("SHA1");
byte[] messageDigest = md.digest(str.getBytes(StandardCharsets.UTF_8));
System.out.println("SHA1 hash: " + new String(encodeHex(messageDigest)));
md = MessageDigest.getInstance("SHA-256");
messageDigest = md.digest(str.getBytes(StandardCharsets.UTF_8));
System.out.println("SHA256 hash: " + new String(encodeHex(messageDigest)));
md = MessageDigest.getInstance("SHA-512");
messageDigest = md.digest(str.getBytes(StandardCharsets.UTF_8));
System.out.println("SHA512 hash: " + new String(encodeHex(messageDigest)));
gostr := "jianggujin"
encoded := sha1.Sum([]byte(str))
fmt.Println("SHA1 hash:", hex.EncodeToString(encoded[:]))
encoded256 := sha256.Sum256([]byte(str))
fmt.Println("SHA256 hash:", hex.EncodeToString(encoded256[:]))
encoded512 := sha512.Sum512([]byte(str))
fmt.Println("SHA512 hash:", hex.EncodeToString(encoded512[:]))
运行并观察控制台输出:
SHA1 hash: 26635165490065775f7edee885392424852da15a SHA256 hash: e99631966983730acf4c1fa45669227980c19ef1f7fbea8fab2dc897b881cd30 SHA512 hash: 6a677109e89384062e6078767417ed3176128451ecb91fae59bb3f407a2752579e6c99de14af00c7d8abacc78395cd587bfd945b9045f8ca227bb2fc8b140cda
SM3
是中国国家密码管理局(中国密码学会)制定的一种密码哈希算法,用于产生消息的哈希值。它是中国政府采用的国家密码算法标准之一,具有自主知识产权。SM3
算法基于分组密码结构,采用了类似于SHA-256
的分组运算、置换运算和轮函数等步骤,但在设计上有一些独特之处。它将消息按照一定的规则分组,然后对每个分组进行一系列的位运算和置换操作,最终生成一个256
位(32
字节)的哈希值。
特点
SM3
算法生成的哈希值长度固定为256
比特(32
字节)。SM3
算法在设计上考虑了碰撞攻击,具有很高的抗碰撞能力。SM3
是中国政府采用的国家密码算法标准,具有自主知识产权,被广泛应用于政府和企业信息安全领域。SM3
算法在计算哈希值时性能高效,适用于大规模数据的哈希计算。使用场景
SM3
算法可用于校验文件完整性,验证数据在传输过程中是否被篡改。SM3
算法可用于生成数字签名,用于验证数据的完整性和真实性。SM3
算法可用于密码学应用中的哈希计算和消息认证等场景。示例代码
javaSecurity.addProvider(new BouncyCastleProvider());
String str = "jianggujin";
MessageDigest md = MessageDigest.getInstance("SM3");
byte[] messageDigest = md.digest(str.getBytes(StandardCharsets.UTF_8));
System.out.println("SM3 hash: " + new String(encodeHex(messageDigest)));
注
Java
默认未提供SM3
实现,示例代码依赖bouncycastle
。
xml<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
<version>1.72</version>
</dependency>
gostr := "jianggujin"
encoded := sm3.Sm3Sum([]byte(str))
fmt.Println("SM3 hash:", hex.EncodeToString(encoded))
注
Go
默认未提供SM3
实现,示例代码依赖github.com/tjfoc/gmsm/sm3
。
运行并观察控制台输出:
SM3 hash: 26b306b040c8b1a31728baf37ac7fb88285356eb20f762a0e2ae9e00ff338dad
消息认证码通常使用对称密钥加密算法,如HMAC
(基于哈希的消息认证码)或CMAC
(Cipher-based MAC
)。消息认证码通常用于保护数据的完整性,防止篡改和伪造攻击。它可以确保消息在传输过程中没有被篡改或伪造,并且只有知道密钥的合法用户才能够验证消息的真实性。
使用场景
解决的问题
MAC
可以确保数据在传输过程中没有被篡改或损坏。接收方可以使用相同的密钥和MAC
算法对接收到的消息计算出一个认证标签,如果接收到的消息的认证标签与计算出的认证标签不一致,说明消息可能已经被篡改,从而保证了数据的完整性。MAC
可以验证消息的真实性,确保消息来自于合法的发送方,并且没有被伪造。只有知道密钥的合法用户才能够计算出正确的MAC
,从而验证消息的真实性。MAC
通常与时间戳或随机数结合使用,以防止重放攻击。通过在消息中包含时间戳或随机数,并将其作为输入计算MAC
,可以确保每个消息都是唯一的,防止攻击者重放之前的消息来欺骗系统。MAC
可以用于验证用户的身份信息的真实性,确保用户具有相应的权限和权限。代表性算法
ChaCha20
结合使用,用于实现高性能的加密和认证。HMAC
(基于哈希的消息认证码)是一种用于验证消息完整性和真实性的密码学算法。它结合了哈希函数和密钥,用于生成一个固定长度的认证码,以确保消息的完整性和真实性。HMAC
算法基于哈希函数和密钥的组合。它将消息和密钥作为输入,通过一系列的哈希计算生成一个固定长度的认证码。在计算过程中,消息被与一个内部的固定值(称为ipad
)进行异或操作,然后再与另一个内部固定值(称为opad
)进行连接,并且再次进行哈希计算,最终生成认证码。
特点
HMAC
可以验证消息的完整性,确保消息在传输过程中没有被篡改。HMAC
使用密钥来生成认证码,只有持有正确密钥的一方才能验证消息的真实性。HMAC
基于哈希函数,具有很高的抗碰撞能力,即使输入消息发生微小变化,也会导致认证码的变化。HMAC
可以使用多种哈希函数和不同长度的密钥,具有一定的灵活性和可配置性。使用场景
HMAC
用于验证消息的真实性和完整性,防止消息被篡改或伪造。HMAC
可以用于身份验证系统中生成和验证令牌,确保只有持有正确密钥的用户才能访问受保护的资源。HMAC
可以用于生成数字签名,用于证明数据的来源和完整性。HMAC
可以用于API
认证中,确保只有经过授权的客户端才能访问API
。示例代码
javabyte[] key = "hmackey".getBytes(StandardCharsets.UTF_8);
String str = "jianggujin";
Mac mac = Mac.getInstance("HmacSHA1");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "HmacSHA1");
mac.init(secretKeySpec);
byte[] macBytes = mac.doFinal(str.getBytes(StandardCharsets.UTF_8));
String encoded = Base64.getEncoder().encodeToString(macBytes);
System.out.println("HMAC-SHA1: " + encoded);
mac = Mac.getInstance("HmacSHA256");
secretKeySpec = new SecretKeySpec(key, "HmacSHA256");
mac.init(secretKeySpec);
macBytes = mac.doFinal(str.getBytes(StandardCharsets.UTF_8));
encoded = Base64.getEncoder().encodeToString(macBytes);
System.out.println("HMAC-SHA256: " + encoded);
mac = Mac.getInstance("HmacSHA512");
secretKeySpec = new SecretKeySpec(key, "HmacSHA512");
mac.init(secretKeySpec);
macBytes = mac.doFinal(str.getBytes(StandardCharsets.UTF_8));
encoded = Base64.getEncoder().encodeToString(macBytes);
System.out.println("HMAC-SHA512: " + encoded);
gokey := []byte("hmackey")
str := "jianggujin"
hasher := hmac.New(sha1.New, key)
hasher.Write([]byte(str))
encoded := hasher.Sum(nil)
fmt.Println("HMAC-SHA1:", base64.StdEncoding.EncodeToString(encoded))
hasher = hmac.New(sha256.New, key)
hasher.Write([]byte(str))
encoded = hasher.Sum(nil)
fmt.Println("HMAC-SHA256:", base64.StdEncoding.EncodeToString(encoded))
hasher = hmac.New(sha512.New, key)
hasher.Write([]byte(str))
encoded = hasher.Sum(nil)
fmt.Println("HMAC-SHA512:", base64.StdEncoding.EncodeToString(encoded))
运行并观察控制台输出:
HMAC-SHA1: ArjjXjWOC4/DUfH+H5kwx60dkac= HMAC-SHA256: 3D77xOtAm6FWkQOKepGH7C8duj5WZQ0WBawPUYMzmbA= HMAC-SHA512: jtK/TWbD6EzngWlxA0FdRixVZR2npb9b5MbgSReTei9cO1A0Jd1TWVQ9mWUSXI5cbdz+mvhuvikFkPcOBXbm2A==
相关信息
消息认证码(Message Authentication Code
, MAC
)算法与摘要运算(通常指的是哈希函数或散列函数)在表面上可能看起来相似,因为它们都从输入数据产生固定长度的输出,但实际上它们的设计目标和应用场景有所不同:
消息认证码(MAC
)算法
MAC
的主要目的是提供消息的完整性和认证。它不仅验证数据是否被篡改,还确保接收者能够验证消息确实来自已知的发送者。MAC
算法在计算过程中需要使用一个密钥。这个密钥是预先共享的,只有合法的发送方和接收方知道,这使得MAC
能够提供认证功能。MAC
提供机密性之外的认证服务,意味着即使消息被截获,没有密钥的第三方无法伪造有效的MAC
值。摘要运算(哈希函数)
MD5
、SHA-1
、SHA-256
)在计算时不涉及密钥,是无密钥的函数。MAC
是带有密钥的,提供消息认证和完整性检查,适合于需要验证消息来源的场景。哈希函数是无密钥的,仅提供完整性校验,不涉及认证,适用于不需要识别发送方身份的情景,如文件完整性验证。两者虽都能检验数据完整性,但MAC
通过密钥的加入,额外提供了认证功能,这是它们之间最本质的区别。
对称加密算法是一种加密和解密过程使用相同密钥的加密技术。这意味着发送方和接收方必须事先共享同一把密钥,并且在保密的前提下保存好这把密钥。加密时,发送方使用该密钥将明文(即未加密的信息)转换为密文(即加密后的信息);解密时,接收方再利用相同的密钥将密文还原回明文。由于加解密使用的是相同的密钥,这类算法也被称作“秘密密钥算法”或“共享密钥算法”。
使用场景
解决的问题
代表性算法
128
位的块大小和多种密钥长度(128
、192
、256
位)为特点。56
位有效密钥长度),已不再被认为是安全的。DES
的一个改进,使用三个密钥对数据进行三次加密,以提高安全性。Bruce Schneier
设计,是一个快速、高效的对称加密算法,适用于多种加密需求。Blowfish
算法的后继者,提供更强的安全性和更大的密钥长度。ChaCha20
的前身,也是一个流加密算法,以其简单和高效而闻名。异或加密是一种简单而古老的加密技术,也称为异或运算加密。它使用异或运算对明文和密钥进行位级别的操作,以生成密文。异或加密的原理非常简单,就是对明文和密钥的每一个比特进行异或运算。异或运算的规则是:如果两个比特相同,则结果为0
;如果两个比特不同,则结果为 1
。因此,当明文和密钥进行异或运算时,可以得到密文。
特点
使用场景
示例代码
java@Test
public void testXOR() throws Exception {
byte[] key = "xorkey".getBytes(StandardCharsets.UTF_8);
String str = "jianggujin";
byte[] encoded = xor(str.getBytes(StandardCharsets.UTF_8), key);
System.out.println("Encoded string: " + Base64.getEncoder().encodeToString(encoded));
String decoded = new String(xor(encoded, key));
System.out.println("Decoded string: " + decoded);
}
private byte[] xor(byte[] data, byte[] key) {
for (int i = 0; i < data.length; i++) {
data[i] ^= key[i % key.length];
}
return data;
}
gofunc TestXOR(t *testing.T) {
key := []byte("xorkey")
str := "jianggujin"
encoded := XOR([]byte(str), key)
fmt.Println("Encoded string:", base64.StdEncoding.EncodeToString(encoded))
decoded := XOR(encoded, key)
fmt.Println("Decoded string:", string(decoded))
}
func XOR(data, key []byte) []byte {
for i, v := range data {
data[i] = v ^ key[i%len(key)]
}
return data
}
运行并观察控制台输出:
Encoded string: EgYTBQIeDQUbBQ== Decoded string: jianggujin
AES
(Advanced Encryption Standard
,高级加密标准)是一种对称密钥加密算法,被广泛应用于保护敏感数据的安全性。它是美国联邦政府采用的加密标准,也是目前最常用的对称加密算法之一。AES
加密算法采用分组密码结构,将明文按照固定长度(128
、192
或256
位)进行分组,然后通过一系列的轮函数和密钥迭代,对每个分组进行加密处理,最终生成密文。AES
共有10
轮、12
轮或14
轮轮函数,取决于密钥长度(128
、192
或256
位)。
特点
AES
加密算法采用了先进的分组密码结构和多轮加密过程,具有较高的安全性,抵抗常见的密码攻击。AES
加密算法在硬件和软件上都有高效的实现,加密和解密速度快,适用于大规模数据的加密处理。AES
支持多种密钥长度(128
、192
或256
位),具有一定的灵活性和可配置性。AES
是美国联邦政府采用的加密标准,被广泛应用于政府、企业和个人的信息安全领域。使用场景
AES
可用于保护敏感数据的机密性,如用户密码、支付信息、个人身份信息等。AES
可用于保护网络通信的安全性,如HTTPS
、SSL/TLS
等协议中的数据加密。AES
可用于加密存储介质中的数据,如硬盘、数据库、云存储等。AES
可用于生成数字签名,用于验证数据的完整性和真实性。在使用AES
加密算法时,除了指定密钥长度外,还需要指定工作模式(Mode of Operation
)和填充模式(Padding Scheme
)。
工作模式(Mode of Operation)
工作模式定义了对待加密的数据块的方式,常见的工作模式包括:
ECB
模式不提供数据的机密性和完整性保护。CFB
模式,但是输出的是密钥流而不是密文块。IV
)与计数器相结合产生密钥流,然后将明文与密钥流进行异或操作来加密数据。填充模式(Padding Scheme)
填充模式用于处理明文块的长度与加密算法要求的数据块长度不匹配的情况。常见的填充模式包括:
PKCS#5
或PKCS#7
标准的填充方案,将填充字节的值设置为填充的长度,解密时可通过填充长度来识别和去除填充。相关信息
PKCS5Padding
和PKCS7Padding
都是填充方案,用于在加密过程中对明文进行填充,以满足加密算法要求的数据块长度的整数倍。
主要区别
1
到8
字节之间(包含),因此适用于块长度为64
位(8
字节)的加密算法。1
到255
字节之间的加密算法。总体来说,PKCS7Padding
是PKCS5Padding
的一个超集,它可以处理更广泛的块长度范围。在实践中,PKCS7Padding
更常见,并且在大多数情况下,PKCS7Padding
也适用于 PKCS5Padding
所适用的情况。
示例代码(AES/CBC/PKCS5Padding
)
javabyte[] key = "AD42F6697B035B7580E4FEF93BE20BAD".getBytes(StandardCharsets.UTF_8);
byte[] iv = "AD42F6697B035B75".getBytes(StandardCharsets.UTF_8);
String str = "jianggujin";
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
// 不需要设置向量的工作模式采用如下方式
// cipher.init(Cipher.ENCRYPT_MODE, secretKey);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
byte[] encrypted = cipher.doFinal(str.getBytes(StandardCharsets.UTF_8));
System.out.println("Encrypted string: " + Base64.getEncoder().encodeToString(encrypted));
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
secretKey = new SecretKeySpec(key, "AES");
// 不需要设置向量的工作模式采用如下方式
// cipher.init(Cipher.DECRYPT_MODE, secretKey);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
byte[] decrypted = cipher.doFinal(encrypted);
System.out.println("Decrypted string: " + new String(decrypted, StandardCharsets.UTF_8));
gofunc TestAESCBCPKCS5Padding(t *testing.T) {
key := []byte("AD42F6697B035B7580E4FEF93BE20BAD")
iv := []byte("AD42F6697B035B75")
str := "jianggujin"
block, _ := aes.NewCipher(key)
encrypted, _ := CBC(block, iv, []byte(str), true, aes.BlockSize)
fmt.Println("Encrypted string:", base64.StdEncoding.EncodeToString(encrypted))
decoded, _ := CBC(block, iv, encrypted, false, aes.BlockSize)
fmt.Println("Decrypted string:", string(decoded))
}
// cbc模式加解密
func CBC(block cipher.Block, iv []byte, in []byte, mode bool, blockSize int)
(out []byte, err error) {
var inData []byte
if mode {
inData = pkcs7Padding(in, blockSize)
} else {
inData = in
}
// 如后续出现IV受影响,则需要复制新的IV数据
//iv := make([]byte, blockSize)
//copy(iv, o.Iv)
out = make([]byte, len(inData))
if mode {
cipher.NewCBCEncrypter(block, iv).CryptBlocks(out, inData)
} else {
cipher.NewCBCDecrypter(block, iv).CryptBlocks(out, inData)
out, err = pkcs7UnPadding(out, blockSize)
}
return out, err
}
// PKCS7Padding
// 假设每个区块大小为blockSize
// <1>已对齐,填充一个长度为blockSize且每个字节均为blockSize的数据。
// <2>未对齐,需要补充的字节个数为n,则填充一个长度为n且每个字节均为n的数据。
func pkcs7Padding(src []byte, blockSize int) []byte {
padding := blockSize - len(src)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(src, padtext...)
}
// 在解密后,需要将填充的字符去掉,取最后一位即知道存在多少个补充位
func pkcs7UnPadding(src []byte, blockSize int) ([]byte, error) {
length := len(src)
unpadding := int(src[length-1])
if unpadding > blockSize || unpadding == 0 {
return nil, errors.New("invalid pkcs7 padding (unpadding > BlockSize ||
unpadding == 0)")
}
pad := src[len(src)-unpadding:]
for i := 0; i < unpadding; i++ {
if pad[i] != byte(unpadding) {
return nil, errors.New("invalid pkcs7 padding (pad[i] != unpadding)")
}
}
return src[:(length - unpadding)], nil
}
运行并观察控制台输出:
Encrypted string: H8dXZjZthY6iw+6hmjY71A== Decrypted string: jianggujin
DES
(Data Encryption Standard
,数据加密标准)是一种对称密钥加密算法,是最早广泛应用的分组密码算法之一。DES
使用64
位密钥对64
位的数据块进行加密和解密,经过多轮的置换和替换操作,生成密文。DES
加密算法采用分组密码结构,将明文按照固定长度(64
位)进行分组,然后通过一系列的置换、替换和轮函数,对每个分组进行加密处理,最终生成密文。DES
共有16
轮轮函数,每轮使用不同的子密钥对数据进行处理。
特点
DES
曾经是加密领域的标准,但随着计算能力的增强,现已不再安全,易受到穷举搜索等攻击。DES
的密钥长度固定为64
位,其中8
位用作奇偶校验,实际有效密钥长度为56
位。DES
只提供加密功能,没有提供完整性验证或认证功能。DES
的加密和解密速度相对较快,适用于对称加密领域。使用场景
DES
加密算法,需要维护和兼容。DES
曾经是密码学领域的研究热点,对于学习密码学的学生和研究人员具有一定的参考意义。DES
进行数据加密,如某些内部通信或测试系统。工作模式与填充模式参见
AES
部分内容.
示例代码(DES/CBC/PKCS5Padding
)
javabyte[] key = "12345678".getBytes(StandardCharsets.UTF_8);
byte[] iv = "12345678".getBytes(StandardCharsets.UTF_8);
String str = "jianggujin";
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
SecretKeySpec secretKey = new SecretKeySpec(key, "DES");
// 不需要设置向量的工作模式采用如下方式
// cipher.init(Cipher.ENCRYPT_MODE, secretKey);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
byte[] encrypted = cipher.doFinal(str.getBytes(StandardCharsets.UTF_8));
System.out.println("Encrypted string: " + Base64.getEncoder().encodeToString(encrypted));
cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
secretKey = new SecretKeySpec(key, "DES");
// 不需要设置向量的工作模式采用如下方式
// cipher.init(Cipher.DECRYPT_MODE, secretKey);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
byte[] decrypted = cipher.doFinal(encrypted);
System.out.println("Decrypted string: " + new String(decrypted, StandardCharsets.UTF_8));
gokey := []byte("12345678")
iv := []byte("12345678")
str := "jianggujin"
block, _ := des.NewCipher(key)
encrypted, _ := CBC(block, iv, []byte(str), true, des.BlockSize)
fmt.Println("Encrypted string:", base64.StdEncoding.EncodeToString(encrypted))
decoded, _ := CBC(block, iv, encrypted, false, des.BlockSize)
fmt.Println("Decrypted string:", string(decoded))
运行并观察控制台输出:
Encrypted string: olFmr5i2TfrLnA5c1mz3pA== Decrypted string: jianggujin
3DES
(Triple Data Encryption Standard
)是对DES
加密算法的改进版本,通过对数据应用三次DES
加密来提高安全性。它采用了三个不同的密钥对数据进行加密和解密,从而增强了加密的强度和安全性。在3DES
中包含了两种不同的加密模式:双倍长密钥模式(Two-Key Triple DES
)和三倍长密钥模式(Three-Key Triple DES
)。
56
位的密钥,分别作为第一轮和第三轮的密钥,而第二轮的密钥与第一轮相同。56
位的密钥,分别作为三轮的密钥。特点
3DES
使用了三次DES
加密,比单次DES
加密更加安全,对抗穷举搜索攻击的能力更强。3DES
兼容DES
加密算法,可以在不修改现有系统的情况下进行升级。3DES
是DES
的直接后继者,保留了DES
的许多特点和优点,同时增加了更高的安全性。DES
加密和解密操作,3DES
的性能较低,不适合在对性能要求较高的场景中使用。使用场景
DES
加密算法的遗留系统,可以通过将DES
替换为 3DES 来提高安全性。3DES
加密算法来保护敏感数据。3DES
作为经典的加密算法,仍然具有一定的研究价值,用于密码学领域的学术研究和教学。工作模式与填充模式参见
AES
部分内容.
示例代码(DESede/CBC/PKCS5Padding
)
javabyte[] key = "123456781234567812345678".getBytes(StandardCharsets.UTF_8);
byte[] iv = "12345678".getBytes(StandardCharsets.UTF_8);
String str = "jianggujin";
Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
SecretKeySpec secretKey = new SecretKeySpec(key, "DESede");
// 不需要设置向量的工作模式采用如下方式
// cipher.init(Cipher.ENCRYPT_MODE, secretKey);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
byte[] encrypted = cipher.doFinal(str.getBytes(StandardCharsets.UTF_8));
System.out.println("Encrypted string: " + Base64.getEncoder().encodeToString(encrypted));
cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
secretKey = new SecretKeySpec(key, "DESede");
// 不需要设置向量的工作模式采用如下方式
// cipher.init(Cipher.DECRYPT_MODE, secretKey);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
byte[] decrypted = cipher.doFinal(encrypted);
System.out.println("Decrypted string: " + new String(decrypted, StandardCharsets.UTF_8));
gokey := []byte("123456781234567812345678")
iv := []byte("12345678")
str := "jianggujin"
block, _ := des.NewTripleDESCipher(key)
encrypted, _ := CBC(block, iv, []byte(str), true, des.BlockSize)
fmt.Println("Encrypted string:", base64.StdEncoding.EncodeToString(encrypted))
decoded, _ := CBC(block, iv, encrypted, false, des.BlockSize)
fmt.Println("Decrypted string:", string(decoded))
运行并观察控制台输出:
Encrypted string: olFmr5i2TfrLnA5c1mz3pA== Decrypted string: jianggujin
SM4
是一种分组密码算法,也称为国密算法,由中国国家密码管理局设计并公开的。它是一种对称加密算法,适用于数据加密和解密,以及数据完整性验证等场景。SM4
算法基于Feistel
网络结构,具有32
轮加密和解密过程。它采用了非线性变换、置换运算和密钥混合技术,通过对数据块的多次迭代处理,实现数据的加密和解密。
特点
SM4
采用了混沌运算和S
盒代替传统的置换运算,增强了算法的非线性特性,提高了安全性。SM4
算法具有较高的运算速度,适用于大规模数据加密和解密的场景。SM4
算法已被ISO/IEC
标准化,并被广泛应用于政府、金融、电信等领域。SM4
算法是国家自主设计的密码算法,具有自主可控的特点。使用场景
SM4
算法是中国国家密码管理局发布的国家密码算法,适用于政府和军事领域的数据加密和解密。SM4
算法已被广泛应用于金融、电信等领域的数据加密和安全通信。SM4
算法可以用于保护数据的安全性。工作模式与填充模式参见
AES
部分内容.
示例代码(SM4/CBC/PKCS5Padding
)
javaSecurity.addProvider(new BouncyCastleProvider());
byte[] key = "1234567812345678".getBytes(StandardCharsets.UTF_8);
byte[] iv = "1234567812345678".getBytes(StandardCharsets.UTF_8);
String str = "jianggujin";
Cipher cipher = Cipher.getInstance("SM4/CBC/PKCS5Padding");
SecretKeySpec secretKey = new SecretKeySpec(key, "SM4");
// 不需要设置向量的工作模式采用如下方式
// cipher.init(Cipher.ENCRYPT_MODE, secretKey);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
byte[] encrypted = cipher.doFinal(str.getBytes(StandardCharsets.UTF_8));
System.out.println("Encrypted string: " + Base64.getEncoder().encodeToString(encrypted));
cipher = Cipher.getInstance("SM4/CBC/PKCS5Padding");
secretKey = new SecretKeySpec(key, "SM4");
// 不需要设置向量的工作模式采用如下方式
// cipher.init(Cipher.DECRYPT_MODE, secretKey);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
byte[] decrypted = cipher.doFinal(encrypted);
System.out.println("Decrypted string: " + new String(decrypted, StandardCharsets.UTF_8));
注
Java
默认未提供SM4
实现,示例代码依赖bouncycastle
。
xml<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
<version>1.72</version>
</dependency>
gokey := []byte("1234567812345678")
iv := []byte("1234567812345678")
str := "jianggujin"
block, _ := sm4.NewCipher(key)
encrypted, _ := CBC(block, iv, []byte(str), true, sm4.BlockSize)
fmt.Println("Encrypted string:", base64.StdEncoding.EncodeToString(encrypted))
decoded, _ := CBC(block, iv, encrypted, false, sm4.BlockSize)
fmt.Println("Decrypted string:", string(decoded))
注
Go
默认未提供SM4
实现,示例代码依赖github.com/tjfoc/gmsm/sm4
。
运行并观察控制台输出:
Encrypted string: KastFVtO8pcdAWTmGs/s1Q== Decrypted string: jianggujin
非对称加密算法是一种加密和解密过程使用不同密钥的加密技术,它需要一对密钥:公开密钥(公钥)和私有密钥(私钥)。公钥可以公开给任何人,用于加密信息,而私钥必须保密,仅由信息的接收者持有,用于解密信息。这种机制解决了密钥分发和安全通信的难题,因为即使公钥广为人知,没有对应的私钥也无法解密信息。非对称加密算法的加密过程和解密过程是数学上相关的,但直接从公钥推算出私钥在计算上是不可行的,这确保了系统的安全性。
使用场景
解决的问题
代表性算法
Ron Rivest
、Adi Shamir
和Leonard Adleman
发明,是最早也是最知名的非对称加密算法之一,广泛应用于数字签名和密钥交换。RSA
,使用更短的密钥长度就能达到相同的安全级别,适用于资源受限的环境。DH
密钥交换协议的扩展。RSA
是一种非对称加密算法,是目前应用最广泛的公钥加密算法之一。RSA
算法基于数论中的大数分解问题,其安全性依赖于大整数分解的困难性。RSA
算法基于两个大素数的乘积作为公钥的模数,公钥由模数和指数组成,私钥由模数和另一个指数组成。加密过程使用公钥对数据进行加密,解密过程使用私钥对密文进行解密。RSA
算法的安全性基于大数分解问题的困难性,即给定一个大整数N
,找到两个大素数p
和q
,使得N = p * q
,是一个困难的问题。
特点
RSA
算法使用不同的密钥进行加密和解密,公钥用于加密,私钥用于解密,具有较高的安全性。RSA
算法不仅可以用于加密数据,还可以用于生成和验证数字签名,确保数据的完整性和来源可信性。RSA
算法支持不同长度的密钥,密钥越长,安全性越高,但加密和解密速度越慢。RSA
算法被广泛应用于安全通信、数字签名、身份认证等领域。使用场景
RSA
算法可以用于保护通信中的数据隐私和完整性,例如SSL/TLS
、SSH
等安全协议。RSA
算法可以用于生成和验证数字签名,确保数据的完整性和来源可信性,例如数字证书、电子邮件签名等。RSA
算法可以用于安全地交换对称加密算法的密钥,例如在Diffie-Hellman
密钥交换中使用。RSA
算法可以用于实现身份认证功能,例如在公钥基础设施(PKI
)中用于用户身份验证。示例代码(生成密钥)
javaKeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
keyPairGen.initialize(1024, new SecureRandom());
KeyPair pair = keyPairGen.generateKeyPair();
System.out.println("Private: " + Base64.getEncoder().encodeToString(pair.getPrivate().getEncoded()));
System.out.println("Public: " + Base64.getEncoder().encodeToString(pair.getPublic().getEncoded()));
gokey, _ := rsa.GenerateKey(rand.Reader, 1024)
out, _ := x509.MarshalPKCS8PrivateKey(key)
fmt.Println("Private:", base64.StdEncoding.EncodeToString(out))
publicKey := key.PublicKey
out, _ = x509.MarshalPKIXPublicKey(&publicKey)
fmt.Println("Public:", base64.StdEncoding.EncodeToString(out))
示例代码(加解密)
javaX509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(PUBLIC_KEY));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(keySpec);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
String str = "jianggujin";
byte[] encrypted = cipher.doFinal(str.getBytes(StandardCharsets.UTF_8));
System.out.println("Encrypted string: " + Base64.getEncoder().encodeToString(encrypted));
PKCS8EncodedKeySpec pkcs8KeySpec =
new PKCS8EncodedKeySpec(Base64.getDecoder().decode(PRIVATE_KEY));
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decrypted = cipher.doFinal(encrypted);
System.out.println("Decrypted string: " + new String(decrypted, StandardCharsets.UTF_8));
gostr := "jianggujin"
pubKey, _ := base64.StdEncoding.DecodeString(PUBLIC_KEY)
publicKey, _ := x509.ParsePKIXPublicKey(pubKey)
encrypted, _ := rsa.EncryptPKCS1v15(rand.Reader, publicKey.(*rsa.PublicKey), []byte(str))
fmt.Println("Encrypted string:", base64.StdEncoding.EncodeToString(encrypted))
priKey, _ := base64.StdEncoding.DecodeString(PRIVATE_KEY)
privateKey, _ := x509.ParsePKCS8PrivateKey(priKey)
decrypted, _ := rsa.DecryptPKCS1v15(rand.Reader, privateKey.(*rsa.PrivateKey), encrypted)
fmt.Println("Decrypted string:", string(decrypted))
示例代码(签名验签)
javaString str = "jianggujin";
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec pkcs8KeySpec =
new PKCS8EncodedKeySpec(Base64.getDecoder().decode(PRIVATE_KEY));
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(str.getBytes(StandardCharsets.UTF_8));
byte[] signed = signature.sign();
System.out.println("Signed string: " + Base64.getEncoder().encodeToString(signed));
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(PUBLIC_KEY));
PublicKey publicKey = keyFactory.generatePublic(keySpec);
signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
signature.update(str.getBytes(StandardCharsets.UTF_8));
System.out.println("Verify: " + signature.verify(signed));
gostr := "jianggujin"
priKey, _ := base64.StdEncoding.DecodeString(PRIVATE_KEY)
privateKey, _ := x509.ParsePKCS8PrivateKey(priKey)
hashed := sha256.Sum256([]byte(str))
signed, _ := rsa.SignPKCS1v15(rand.Reader, privateKey.(*rsa.PrivateKey), crypto.SHA256, hashed[:])
fmt.Println("Signed string:", base64.StdEncoding.EncodeToString(signed))
pubKey, _ := base64.StdEncoding.DecodeString(PUBLIC_KEY)
publicKey, _ := x509.ParsePKIXPublicKey(pubKey)
fmt.Println("Encrypted string:", rsa.VerifyPKCS1v15(publicKey.(*rsa.PublicKey),
crypto.SHA256, hashed[:], signed) == nil)
相关信息
签名算法是一种用于确保数据完整性和认证数据来源的密码学技术。它通常与加密算法结合使用,用于生成数字签名,以便验证数据的真实性和完整性。下面是签名算法的详细介绍和说明:
基本原理
签名算法基于公钥加密和私钥解密的原理。发送方使用私钥对数据进行签名,接收方使用发送方的公钥来验证签名。如果数据在传输过程中被篡改,签名验证将失败,因为篡改后的数据与原始签名不匹配。
加密与签名的区别
虽然加密和签名都使用了公钥和私钥,但它们的目的和机制有所不同。加密是为了保护数据的隐私性,而签名是为了确保数据的完整性和认证数据来源。
签名的过程
验证签名的过程
SM2
是一种基于椭圆曲线密码学(ECC
)的国密算法,由中国密码学家提出,用于数字签名、密钥交换、公钥加密等场景。SM2
算法是中国政府采用的国家密码算法标准之一。SM2
算法基于椭圆曲线离散对数问题,其核心原理是在椭圆曲线上进行点运算,实现数字签名、密钥交换等功能。SM2
算法采用了一套基于国际标准的椭圆曲线参数,具有较高的安全性和效率。
特点
SM2
算法是中国政府采用的国家密码算法标准,具有较高的安全性和可信度。SM2
算法基于椭圆曲线密码学,具有较高的安全性和效率。SM2
算法支持数字签名功能,可以用于数据认证、身份认证等场景。SM2
算法支持密钥交换功能,可以安全地交换密钥,用于加密通信。SM2
算法具有较高的运算效率,适用于大规模数据的加密和解密。使用场景
SM2
算法可以用于安全通信场景,如SSL/TLS
协议中的数字签名和密钥交换过程。SM2
算法可以用于数字签名场景,如电子合同签署、电子证书认证等。SM2
算法可以用于密钥交换场景,如密钥协商、密钥派生等。SM2
算法的公钥和私钥有多种格式类型,每种格式都有其特定的用途和特点。常用格式与说明如下:
私钥格式
PKCS#1
格式,这是一种较为传统的RSA
私钥格式,但也可以用在ECC
上。PKCS#8
是一种更通用的私钥表示方式,它包含了私钥以及一些额外的信息,如算法标识符。公钥格式
04
开头,后跟两个256
位数字;一个用于点的x
坐标,另一个用于点的y
坐标。04
,只包含x
和y
坐标的值。ASN.1 DER
编码格式表示,这种格式在证书和某些加密系统中广泛使用。PKCS#8
格式,公钥也可以使用PKCS#8
格式,它同样包含了公钥以及一些算法参数和标识符。相关信息
在讨论SM2
算法时,经常会提到的D值
、Q值(压缩与未压缩)
、Q值x
、Q值y
,这些术语与椭圆曲线密码学(ECC
)中的点表示和密钥生成过程相关。基本解释如下:
D值:在椭圆曲线密码学中,D值
通常指的是私钥。它是用户选择的一个随机大整数,用来与椭圆曲线上的一点进行点乘运算,从而得到对应的公钥点。在SM2
算法中,D值
是私钥的表示,用于签名和解密操作。示例:761cdbbaeb1203adbf2afce9777ff6c247c8f30454055329d518bb429f330b99
。
Q值(压缩与未压缩):Q值
通常指的是公钥,在椭圆曲线密码学中,公钥是由椭圆曲线上的一点表示的。这个点可以以两种形式存储或传输:
x坐标
和y坐标
,通常会有额外的字节来标识是哪个坐标在哪个半平面(通常是通过一个额外的字节,如0x04
,后面跟着x
和y
坐标)。示例:04641B729BB329D780B853DA48B9755015B6C7ABE21EF9B61DF4D855BC1CBE41114C34776539182D0DFA6CA002959C72F2B6BE1B05DE5DD1A41C47F650DB780BB9
。x坐标
,并且根据y坐标
的奇偶性添加一个额外的字节(0x02
表示y
是偶数,0x03
表示y
是奇数)。这样可以节省存储空间,尤其是在带宽敏感的应用中。示例:03641B729BB329D780B853DA48B9755015B6C7ABE21EF9B61DF4D855BC1CBE4111
。Q值x:指的是公钥点在压缩或未压缩格式中明确表示的x坐标
。它是椭圆曲线上点的横坐标部分。示例:641B729BB329D780B853DA48B9755015B6C7ABE21EF9B61DF4D855BC1CBE4111
。
Q值y:指的是公钥点的y坐标
。在未压缩格式的公钥表示中会明确包括y坐标
,而在压缩格式中,y坐标
不直接给出,但可以通过x坐标
和椭圆曲线方程计算得到。示例:4C34776539182D0DFA6CA002959C72F2B6BE1B05DE5DD1A41C47F650DB780BB9
。
总的来说,D值
是私钥,Q值
代表公钥,而Q值x
和Q值y
分别是公钥点在坐标平面上的x坐标
和y坐标
,它们根据压缩与否的不同格式,可能以不同的方式表示或传输。
加密数据格式
SM2
加密后的数据通常包含三个部分:随机产生的公钥C1
、密文C2
和使用SM3
算法计算得到的消息摘要C3
。ASN.1
格式表示,这在某些硬件加密机中使用。相关信息
SM2
加密数据最初使用C1C2C3
格式数据,在新版标准中为C1C3C2
格式,在实际使用中需要注意区分并在必要时进行数据转换。Java
中常用的bouncycastle
默认使用的格式为C1C2C3
。
签名数据格式
SM2
签名通常由两个部分组成,R和S
,它们直接拼接在一起表示。ASN.1
格式定义,这符合国标(GM/T 0009-2012
)规定。示例代码(生成密钥)
javaSecurity.addProvider(new BouncyCastleProvider());
X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
ECParameterSpec ecParameterSpec = new ECParameterSpec(x9ECParameters.getCurve(),
x9ECParameters.getG(), x9ECParameters.getN());
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("EC", "BC");
keyPairGen.initialize(ecParameterSpec, new SecureRandom());
KeyPair pair = keyPairGen.generateKeyPair();
BCECPrivateKey privateKey = (BCECPrivateKey) pair.getPrivate();
BCECPublicKey publicKey = (BCECPublicKey) pair.getPublic();
System.out.println("D值: " + privateKey.getD().toString(16));
System.out.println("Q值(未压缩): " + new String(encodeHex(publicKey.getQ().getEncoded(false))));
System.out.println("Q值(压缩): " + new String(encodeHex(publicKey.getQ().getEncoded(true))));
System.out.println("Q值X: " + publicKey.getQ().getAffineXCoord());
System.out.println("Q值Y: " + publicKey.getQ().getAffineYCoord());
注
Java
默认未提供SM2
实现,示例代码依赖bouncycastle
。
xml<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
<version>1.72</version>
</dependency>
gokey, _ := sm2.GenerateKey(rand.Reader)
fmt.Println("D值:", key.D.Text(16))
fmt.Println("Q值X:", key.X.Text(16))
fmt.Println("Q值Y:", key.Y.Text(16))
注
Go
默认未提供SM2
实现,示例代码依赖github.com/tjfoc/gmsm/sm2
。
示例代码(逆向公私钥)
javaX9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
ECParameterSpec ecParameterSpec = new ECParameterSpec(x9ECParameters.getCurve(),
x9ECParameters.getG(), x9ECParameters.getN());
String priKey = "MIICSwIBADCB7AYHKoZIzj0CATCB4AIBATAsBgcqhkjOPQEBAiEA/////v////////////////////8AAAAA" +
"//////////8wRAQg/////v////////////////////8AAAAA//////////wEICjp" +
"+p6dn140TVqeS89lCafzl4n1FauPkt28vUFNlA6TBEEEMsSuLB8ZgRlfmQRGajnJlI/jC7" +
"/yZgvhcVpFiTNMdMe8Nzai9PZ3nFm9zuNraSFT0KmHfMYqR0AC3zLlITnwoAIhAP////7" +
"///////////////9yA99rIcYFK1O79Ak51UEjAgEBBIIBVTCCAVECAQEEIHYc27rrEgOtvyr86Xd" +
"/9sJHyPMEVAVTKdUYu0KfMwuZoIHjMIHgAgEBMCwGByqGSM49AQECIQD////+/////////////////////wAAAAD" +
"//////////zBEBCD////+/////////////////////wAAAAD//////////AQgKOn6np2fXjRNWp5Lz2UJp/OXifUVq4" +
"+S3by9QU2UDpMEQQQyxK4sHxmBGV+ZBEZqOcmUj+MLv/JmC" +
"+FxWkWJM0x0x7w3NqL09necWb3O42tpIVPQqYd8xipHQALfMuUhOfCgAiEA/////v" +
"///////////////3ID32shxgUrU7v0CTnVQSMCAQGhRANCAARkG3KbsynXgLhT2ki5dVAVtser4h75th302FW8HL5BEUw0d2U5GC0N+mygApWccvK2vhsF3l3RpBxH9lDbeAu5";
String pubKey = "MIIBMzCB7AYHKoZIzj0CATCB4AIBATAsBgcqhkjOPQEBAiEA/////v////////////////////8AAAAA" +
"//////////8wRAQg/////v////////////////////8AAAAA//////////wEICjp" +
"+p6dn140TVqeS89lCafzl4n1FauPkt28vUFNlA6TBEEEMsSuLB8ZgRlfmQRGajnJlI/jC7" +
"/yZgvhcVpFiTNMdMe8Nzai9PZ3nFm9zuNraSFT0KmHfMYqR0AC3zLlITnwoAIhAP////7" +
"///////////////9yA99rIcYFK1O79Ak51UEjAgEBA0IABGQbcpuzKdeAuFPaSLl1UBW2x6viHvm2HfTYVbwcvkERTDR3ZTkYLQ36bKAClZxy8ra+GwXeXdGkHEf2UNt4C7k=";
String X = "641b729bb329d780b853da48b9755015b6c7abe21ef9b61df4d855bc1cbe4111";
String Y = "4c34776539182d0dfa6ca002959c72f2b6be1b05de5dd1a41c47f650db780bb9";
String Q = "04641B729BB329D780B853DA48B9755015B6C7ABE21EF9B61DF4D855BC1CBE41114C34776539182D0DFA6CA002959C72F2B6BE1B05DE5DD1A41C47F650DB780BB9";
//String Q = "03641B729BB329D780B853DA48B9755015B6C7ABE21EF9B61DF4D855BC1CBE4111";
String D = "761cdbbaeb1203adbf2afce9777ff6c247c8f30454055329d518bb429f330b99";
// 尝试D值
ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(new BigInteger(D, 16), ecParameterSpec);
BCECPrivateKey privateKey = new BCECPrivateKey("EC", ecPrivateKeySpec, BouncyCastleProvider.CONFIGURATION);
System.out.println("私钥: " + privateKey.getD().toString(16));
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(priKey));
KeyFactory keyFactory = KeyFactory.getInstance("EC");
privateKey = (BCECPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
System.out.println("私钥: " + privateKey.getD().toString(16));
// 尝试Q值
ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(
x9ECParameters.getCurve().decodePoint(HexUtil.decodeHex(Q)), ecParameterSpec);
BCECPublicKey publicKey = new BCECPublicKey("EC", ecPublicKeySpec, BouncyCastleProvider.CONFIGURATION);
System.out.println("公钥X:" + publicKey.getQ().getAffineXCoord());
System.out.println("公钥Y:" + publicKey.getQ().getAffineYCoord());
System.out.println("公钥: " + HexUtil.encodeHexStr(publicKey.getQ().getEncoded(false)).toUpperCase());
System.out.println("公钥: " + HexUtil.encodeHexStr(publicKey.getQ().getEncoded(true)).toUpperCase());
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(pubKey));
publicKey = (BCECPublicKey) keyFactory.generatePublic(keySpec);
System.out.println("公钥X:" + publicKey.getQ().getAffineXCoord());
System.out.println("公钥Y:" + publicKey.getQ().getAffineYCoord());
System.out.println("公钥: " + HexUtil.encodeHexStr(publicKey.getQ().getEncoded(false)).toUpperCase());
System.out.println("公钥: " + HexUtil.encodeHexStr(publicKey.getQ().getEncoded(true)).toUpperCase());
ecPublicKeySpec = new ECPublicKeySpec(
x9ECParameters.getCurve().createPoint(new BigInteger(X, 16), new BigInteger(Y, 16)), ecParameterSpec);
publicKey = new BCECPublicKey("EC", ecPublicKeySpec, BouncyCastleProvider.CONFIGURATION);
System.out.println("公钥X:" + publicKey.getQ().getAffineXCoord());
System.out.println("公钥Y:" + publicKey.getQ().getAffineYCoord());
System.out.println("公钥: " + HexUtil.encodeHexStr(publicKey.getQ().getEncoded(false)).toUpperCase());
System.out.println("公钥: " + HexUtil.encodeHexStr(publicKey.getQ().getEncoded(true)).toUpperCase());
示例代码(加解密)
javaPublicKey publicKey = null; // 参考上面内容获取
Cipher cipher = Cipher.getInstance("SM2");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
String str = "jianggujin";
byte[] encrypted = cipher.doFinal(str.getBytes(StandardCharsets.UTF_8));
System.out.println("Encrypted string: " + Base64.getEncoder().encodeToString(encrypted));
PrivateKey privateKey = null; // 参考上面内容获取
cipher = Cipher.getInstance("SM2");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decrypted = cipher.doFinal(encrypted);
System.out.println("Decrypted string: " + new String(decrypted, StandardCharsets.UTF_8));
示例代码(签名验签)
javaString str = "jianggujin";
PrivateKey privateKey = null; // 参考上面内容获取
Signature signature = Signature.getInstance("SM3withSM2");
signature.initSign(privateKey);
signature.update(str.getBytes(StandardCharsets.UTF_8));
byte[] signed = signature.sign();
System.out.println("Signed string: " + Base64.getEncoder().encodeToString(signed));
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(PUBLIC_KEY));
PublicKey publicKey = null; // 参考上面内容获取
signature = Signature.getInstance("SM3withSM2");
signature.initVerify(publicKey);
signature.update(str.getBytes(StandardCharsets.UTF_8));
System.out.println("Verify: " + signature.verify(signed));
示例代码(C1C2C3 <=> C1C3C2)
javaprivate final X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
/**
* bc加解密使用旧标c1||c2||c3,此方法在加密后调用,将结果转化为c1||c3||c2
*/
private byte[] changeC1C2C3ToC1C3C2(byte[] c1c2c3) {
// sm2p256v1的这个固定65。可看GMNamedCurves、ECCurve代码。
final int c1Len = (this.x9ECParameters.getCurve().getFieldSize() + 7) / 8 * 2 + 1;
final int c3Len = 32; // new SM3Digest().getDigestSize();
byte[] result = new byte[c1c2c3.length];
System.arraycopy(c1c2c3, 0, result, 0, c1Len); // c1
System.arraycopy(c1c2c3, c1c2c3.length - c3Len, result, c1Len, c3Len); // c3
System.arraycopy(c1c2c3, c1Len, result, c1Len + c3Len, c1c2c3.length - c1Len - c3Len); // c2
return result;
}
/**
* bc加解密使用旧标c1||c3||c2,此方法在解密前调用,将密文转化为c1||c2||c3再去解密
*/
private byte[] changeC1C3C2ToC1C2C3(byte[] c1c3c2) {
// sm2p256v1的这个固定65。可看GMNamedCurves、ECCurve代码。
final int c1Len = (this.x9ECParameters.getCurve().getFieldSize() + 7) / 8 * 2 + 1;
final int c3Len = 32; // new SM3Digest().getDigestSize();
byte[] result = new byte[c1c3c2.length];
System.arraycopy(c1c3c2, 0, result, 0, c1Len); // c1: 0->65
System.arraycopy(c1c3c2, c1Len + c3Len, result, c1Len, c1c3c2.length - c1Len - c3Len); // c2
System.arraycopy(c1c3c2, c1Len, result, c1c3c2.length - c3Len, c3Len); // c3
return result;
}
示例代码(ASN1 <=> R_S)
javaprivate final X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
private final int RS_LEN = 32;
/**
* BC的SM3withSM2签名得到的结果的rs是asn1格式的,这个方法转化成直接拼接r||s
*/
public byte[] rsAsn1ToPlain(byte[] rsDer) throws Exception {
BigInteger[] decode = StandardDSAEncoding.INSTANCE.decode(this.x9ECParameters.getN(), rsDer);
byte[] r = this.bigIntToFixedLengthBytes(decode[0]);
byte[] s = this.bigIntToFixedLengthBytes(decode[1]);
byte[] result = new byte[r.length + s.length];
System.arraycopy(r, 0, result, 0, r.length);
System.arraycopy(s, 0, result, r.length, s.length);
return result;
}
/**
* BC的SM3withSM2验签需要的rs是asn1格式的,这个方法将直接拼接r||s的字节数组转化成asn1格式
*/
public byte[] rsPlainToAsn1(byte[] sign) throws Exception {
if (sign.length != RS_LEN * 2) {
throw new IllegalArgumentException("err rs. ");
}
BigInteger r = new BigInteger(1, Arrays.copyOfRange(sign, 0, RS_LEN));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(sign, RS_LEN, RS_LEN * 2));
return StandardDSAEncoding.INSTANCE.encode(this.x9ECParameters.getN(), r, s);
}
/**
* BigInteger转固定长度bytes
*/
private byte[] bigIntToFixedLengthBytes(BigInteger rOrS) {
// for sm2p256v1, n is 00fffffffeffffffffffffffffffffffff7203df6b21c6052b53bbf40939d54123,
// r and s are the result of mod n, so they should be less than n and have length<=32
byte[] rs = rOrS.toByteArray();
if (rs.length == RS_LEN) {
return rs;
} else if (rs.length == RS_LEN + 1 && rs[0] == 0) {
return Arrays.copyOfRange(rs, 1, RS_LEN + 1);
} else if (rs.length < RS_LEN) {
byte[] result = new byte[RS_LEN];
Arrays.fill(result, (byte) 0);
System.arraycopy(rs, 0, result, RS_LEN - rs.length, rs.length);
return result;
} else {
throw new RuntimeException("Error rs: " + CodecUtils.encodeHexString(rs, true));
}
}
CRC
(循环冗余校验)算法主要用于数据通信领域中的错误检测,它是一种广泛应用于数字网络和存储设备中的差错控制方法。
CRC算法的分类
CRC
算法并没有严格的分类体系,但根据生成多项式的不同,可以有多种不同的CRC
算法,常见的包括:
8
位生成多项式,如CRC-8-ATM
。16
位生成多项式,如CRC-16-CCITT
、CRC-16-ANSI
等。32
位生成多项式,是通信领域中最常用的,如CRC-32-IEEE 802.3
。64
位生成多项式,适用于需要更高检测能力的场合。CRC算法解决的问题
CRC
算法主要解决的是数据传输过程中的比特错误问题,通过计算数据流与特定生成多项式之间的模二除法余数,生成一个校验码附加到数据后面。接收端重新计算CRC
并与接收到的CRC
校验码比较,如果一致,则认为数据在传输过程中没有发生错误或者发生了可检测的错误。
与信息摘要的异同
CRC
算法和信息摘要算法(如MD5
、SHA
系列)都属于散列函数的范畴,它们都接受输入数据并产生固定长度的输出,且输出对输入非常敏感,即使输入数据有微小变化,输出也会显著不同。CRC
主要用于错误检测,侧重于检测数据在传输或存储过程中的随机错误;而信息摘要算法主要用于数据完整性和身份验证,侧重于防止数据被篡改或确保数据来源的可靠性。CRC
算法在设计上并未考虑抗碰撞性,容易受到精心构造的攻击。128
位、256
位等),且算法更为复杂,适用于安全认证;CRC
的输出较短(如8
位、16
位、32
位等),计算相对简单,适用于快速错误检测。与消息认证算法的异同
HMAC
、CMAC
)除了验证数据完整性外,还常用于提供消息认证服务,确保数据来源的真实性,适用于安全通信。而CRC
主要解决物理层或链路层的数据传输错误,不直接提供安全认证功能。CRC
算法不依赖密钥,安全性较低。CRC
算法复杂,计算成本较高,但在需要高度安全的环境下是必要的。示例代码
javaString str = "jianggujin";
CRC32 crc32 = new CRC32();
crc32.update(str.getBytes(StandardCharsets.UTF_8));
System.out.println("CRC32 value: " + crc32.getValue());
gostr := "jianggujin"
value := crc32.ChecksumIEEE([]byte(str))
fmt.Println("CRC32 value:", value)
运行并观察控制台输出:
CRC32 value: 724585211
SM9
算法属于基于身份的密码。基于身份的密码是一种“高级”的公钥密码方案,在具备常规公钥密码加密、签名等密码功能的同时,基于身份的密码体系不需要CA
中心和数字证书体系。SM9
方案的基本原理是,可以由用户的唯一身份ID
(如对方的电子邮件地址、域名或ID
号等),从系统的全局主密钥中导出对应的私钥或公钥,导出密钥的正确性是由算法保证的,因此在进行加密、验签的时候,只需要获得解密方或签名方的ID
即可,不再需要对方的数字证书了。因此如果应用面对的是一个内部的封闭环境,所有参与用户都是系统内用户,那么采用SM9
方案而不是SM2
证书和CA
的方案,可以简化系统的开发、设计和使用,并降低后续CA
体系的维护成本。
对应数字证书体系中的CA
中心,SM9
体系中也存在一个权威中心,用于生成全局的主密钥(MasterKey
),并且为系统中的每个用户生成、分配用户的私钥。和SM2
密钥对一样,SM9
的主密钥也包含私钥和公钥,其中主公钥(PublicMasterKey
)是可以导出并公开给系统中全体用户的。而SM9
中用户的密钥对比较特殊,其中的公钥并不能从私钥中导出,SM9
用户密钥需要包含用户的ID
起到公钥的作用,在加密和验证签名等密码计算中,真正的用户公钥是在计算中,在运行时通过用户ID
从主公钥中导出的。因此从应用的角度看,SM9
中用户的公钥就是一个字符串形式的ID
。
幂等性是指一个操作或接口,无论执行多少次,其结果都是一样的,系统的状态不会因为重复执行而发生改变。换句话说,多次请求的效果与单次请求相同,这对于确保数据一致性特别重要,尤其是在网络不稳定或用户重复提交请求时。
使用场景
实现方式
防篡改旨在保护数据在传输过程中不被未经授权的第三方修改。这通常通过数据签名、消息认证码(MAC
)、数字签名或加密来实现,确保数据的完整性和来源的真实性。
使用场景
实现方式
HTTPS
协议,确保数据在传输过程中不被查看或修改。MAC
值,服务端通过共享密钥验证MAC
,确保数据未被篡改。SSL/TL
S协议,提供端到端的数据加密和完整性保护。防重放机制用于阻止攻击者捕获有效的数据包或请求然后重新发送,以欺骗系统执行非授权的操作。它通过添加时间戳、序列号、一次性令牌或使用加密技术来验证请求的新鲜度和合法性。
使用场景
实现方式
nonce
,拒绝重复的nonce
。防信息泄露指的是采取措施防止敏感信息在未经许可的情况下被披露给未经授权的实体。这包括数据加密、访问控制、最小权限原则、安全审计等手段。
使用场景
SQL
注入、XSS
攻击。实现方式
本文作者:蒋固金
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!