2024-05-17
杂谈
00

目录

第一部分 概述
第二部分 随机数生成算法
2.1 SHA-1 PRNG(伪随机数生成器)
第三部分 信息编码算法
3.1 URL编码
3.2 Base32编码
3.3 Base62编码
3.4 Base64编码
3.5 十六进制编码
第四部分 摘要运算算法(哈希算法)
4.1 MD5
4.2 SHA
4.3 SM3
第五部分 消息认证码(Message Authentication Code,MAC)
5.1 HMAC
第六部分 对称加密算法
6.1 异或加密
6.2 AES
6.3 DES
6.4 3DES
6.5 SM4
第七部分 非对称加密算法
7.1 RSA
7.2 SM2
第八部分 其他常用算法说明
8.1 CRC(循环冗余校验)算法
8.2 SM9基于身份的密码
第九部分 API常用保护措施
9.1 幂等(Idempotency)
9.2 防篡改(Tamper Resistance)
9.3 防重放(Replay Prevention)
9.4 防信息泄露(Information Leakage Prevention)

第一部分 概述

数据加解密算法是信息安全领域的重要组成部分,它们用于保护数据的机密性、完整性和真实性。在实际应用中,综合使用数据加解密算法以构建多层次的安全防护体系。例如,HTTPS协议结合了对称加密(如AES)用于快速数据传输加密,以及非对称加密(如RSA)用于安全地交换对称密钥。同时,使用摘要算法确保数据完整性,并通过数字签名验证消息来源的合法性。这种组合方式既解决了数据的机密性问题,又保障了通信的完整性和认证性,是现代网络安全架构的基石。

算法分类

  • 随机数生成算法:在加密过程中,随机数用于生成密钥、初始化向量(IV)等,以增加加密的安全性。
  • 信息编码算法:信息编码算法将数据转换为另一种格式,使其在传输或存储过程中更容易处理或更安全。
  • 摘要运算算法(哈希算法):摘要运算算法将输入的数据转换为固定长度的哈希值(摘要),通常不可逆。相同的输入将产生相同的哈希值。
  • 消息认证码算法MAC的主要目的是提供消息的完整性和认证。它不仅验证数据是否被篡改,还确保接收者能够验证消息确实来自已知的发送者。
  • 对称加密算法:对称加密算法使用相同的密钥对数据进行加密和解密。加密和解密都使用相同的密钥,因此速度较快。
  • 非对称加密算法:非对称加密算法使用一对密钥(公钥和私钥)进行加密和解密。公钥用于加密,私钥用于解密。公钥可以公开,私钥保密。

作用

  • 保护数据的机密性:防止未授权的个人或实体读取敏感信息。
  • 确保数据的完整性:确保数据在传输或存储过程中未被篡改。
  • 验证数据的真实性:确认数据确实来自声称的发送者,并且是可信的。

使用场景

  • 个人隐私保护:如电子邮件加密、个人文件加密。
  • 企业数据安全:保护商业机密、客户信息等。
  • 在线交易:确保交易数据的安全和完整性。
  • 云存储:保护存储在远程服务器上的敏感数据。
  • 通信安全:如VPNSSL/TLS加密通信。
  • 身份验证:如使用数字签名验证身份和数据来源。

解决的问题

  • 防止数据泄露:通过加密手段保护数据不被未授权访问。
  • 防止数据篡改:使用哈希算法和数字签名确保数据的完整性和真实性。
  • 安全密钥交换:使用非对称加密技术安全地交换用于对称加密的密钥。
  • 密码存储:使用密码哈希算法安全地存储用户密码,即使数据库被泄露,也不会暴露原始密码。

第二部分 随机数生成算法

随机数生成算法是指用于生成看似随机数列的一系列算法,这些数列虽然在计算机中被称为“伪随机数”,因为它们实际上是通过确定性的算法计算出来的,但它们在统计特性上足够接近真正的随机性,从而适用于多种应用场景。计算机生成的随机数依赖于初始种子值,一个好的种子来源(如系统时间、硬件噪声等)能够增加随机数序列的不可预测性。

使用场景

  • 密钥生成:为加密算法生成安全的密钥。
  • 加密算法的初始化向量:确保加密过程中的每个块都不同,提高安全性。
  • 密码重置:生成一次性密码(OTP)用于账户安全。
  • 挑战响应认证:如在SSL/TLS握手过程中生成随机数。
  • 数字签名:生成用于签名算法的随机数。
  • 随机抽样:在需要随机选择的场合,如在线调查或随机分配任务。

解决的问题

  • 不可预测性:确保生成的序列对观察者来说是不可预测的。
  • 均匀性:确保在一定范围内生成的随机数分布均匀。
  • 周期性:减少伪随机数序列的周期性,使得序列在实际应用中不重复。
  • 独立性:保证序列中的每个数与其他数相互独立。

代表性算法

  • 伪随机数生成器(PRNG):基于确定性算法,从一个随机种子开始生成看似随机的数列。常见的PRNG包括:
    • Mersenne Twister:一种广泛使用的PRNG,以其周期长和随机性良好而著称。
    • Yarrow:一种使用SHA-1哈希函数的PRNG,曾被用于生成加密密钥。
  • 真随机数生成器(TRNG):基于物理过程的随机性,如电子噪声或量子效应,来生成随机数。例如:
    • 基于热噪声的随机数芯片:使用电子元件的热噪声来生成随机比特。
    • 量子随机数生成器:利用量子力学的原理,如光子的量子纠缠或量子隧道效应,来生成真正的随机数。

2.1 SHA-1 PRNG(伪随机数生成器)

SHA-1 PRNGPseudorandom 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

第三部分 信息编码算法

信息编码算法是将信息(包括文本、图像、音频、视频等)转换成适合存储、处理、传输的数字形式的过程。编码的目标在于有效地利用有限的资源(如带宽、存储空间)并确保信息的完整性、安全性和高效性。编码不仅仅是简单的二进制转换,还涉及到数据压缩、错误检测与纠正、安全性增强等多个方面。

使用场景

  • 数据存储:将数据编码为适合存储系统的格式。
  • 数据传输:在网络中传输数据时,编码可以减少传输错误。
  • 数据表示:在不同的计算机系统或程序之间交换数据时,需要统一的编码标准。
  • 国际化:支持多语言文本的编码,如UTF-8。
  • 数据压缩:减少数据的存储或传输大小。

解决的问题

  • 效率:通过数据压缩技术减少数据量,提高存储和传输效率。
  • 可靠性:利用信道编码技术(如汉明码、里德-所罗门码)增加数据的容错能力,纠正传输中的错误。
  • 兼容性:确保不同系统和设备间信息的正确解读,如通过统一的字符编码标准。
  • 安全性:加密编码保护数据免受窃听和篡改,确保信息安全传输。
  • 可读性与处理性:对特定类型的数据(如图像、声音)进行格式化编码,便于计算机处理和显示。

代表性算法

  • ASCII(美国信息交换标准代码):一种基于英文字符的编码系统,使用7位或8位二进制数表示字符。
  • UTF-8(8-bit Unicode Transformation Format):一种变长的Unicode字符编码,它可以使用1到4个字节表示一个字符,广泛用于互联网和多语言支持。
  • Base64:一种基于64个可打印字符的编码方法,用于在网络上安全地传输二进制数据。
  • 霍夫曼编码:一种基于符号出现频率进行编码的算法,常用于数据压缩。

3.1 URL编码

URL编码是一种将URL中的特殊字符转换为可安全传输的ASCII字符串的方法。它将非 ASCII字符和特殊字符转换成%后跟两位十六进制数字的形式,以便在URL中进行传输。

使用场景

  • 传输数据:在URL中传输包含特殊字符的数据时,需要进行URL编码以确保传输的安全性和准确性。
  • 构建查询字符串:在构建包含查询参数的URL时,需要对参数值进行URL编码,以避免解析错误和安全问题。
  • 处理表单数据:在Web表单提交中,对表单数据进行URL编码可以确保数据的完整性和正确性。

示例代码

  • Java
java
String 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);
  • Go
go
str := "蒋固金" 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: 蒋固金

3.2 Base32编码

Base32是一种基于32个字符的编码方式,用于将二进制数据转换为可打印的ASCII字符串。它将每5个比特的二进制数据转换为一个字符,因此每个字符表示32种可能性,Base32字符集通常由大写字母A-Z和数字2-7组成。

特点

  • 可打印性Base32编码生成的字符串只包含可打印字符,适用于将二进制数据表示为文本格式。
  • 容错性Base32编码可以容忍少量数据损坏或错误,因为每个字符只包含一部分原始数据。
  • 数据密度低:由于Base32每个字符只能表示5个比特的数据,因此它的数据密度较低,编码后的字符串长度较长。

使用场景

  • URL编码Base32编码可用于URL中传输二进制数据,尤其是在需要避免使用非 ASCII字符的情况下。
  • 二进制数据存储Base32编码可以将二进制数据以文本形式存储在文件或数据库中。
  • 校验和计算Base32编码常用于计算数据的校验和,如CRC32校验等。

示例代码

  • Java
java
String 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>
  • Go
go
str := "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

3.3 Base62编码

Base62编码是一种基于62个字符的编码方式,用于将二进制数据转换为可打印的ASCII字符串。它将每6个比特的二进制数据转换为一个字符,因此每个字符表示62种可能性,Base62字符集通常由大小写字母A-Z和数字0-9组成,共62个字符。。

特点

  • 高效性Base62编码生成的字符串长度相对较短,数据密度高,适用于需要短字符串表示较长数据的场景。
  • 可读性Base62编码生成的字符串包含大小写字母和数字,易于人类阅读和理解。
  • 数据密度高:相较于Base64编码。由于Base62使用了更少的字符,因此在编码相同数量的二进制数据时,理论上可以产生更短的文本字符串。这意味着Base62的数据密度更高,因为它可以用更少的字符表示更多的信息。

使用场景

  • URL编码Base62编码可用于URL中传输二进制数据,尤其是在需要短链接的场景下。
  • 短链接服务Base62编码常用于短链接服务,将长URL转换为短字符串以便分享和传播。
  • 数据存储Base62编码可用于将二进制数据以文本形式存储在文件或数据库中,节省存储空间并提高可读性。

示例代码

  • Java
java
String 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>
  • Go
go
str := "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实现,可使用如下自定义实现。

go
package 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

3.4 Base64编码

Base64编码是一种将二进制数据转换为可打印ASCII字符串的编码方式。它将每6个比特的输入数据转换为一个可打印字符,以便于传输和存储,Base64字符集通常由大小写字母A-Za-z、数字0-9和两个额外的字符(通常是+/)组成,共64个字符。

特点

  • 可打印性Base64编码生成的字符串只包含可打印字符,适用于将二进制数据表示为文本格式。
  • 数据密度低:由于Base64每个字符只能表示6个比特的数据,因此它的数据密度较低,编码后的字符串长度较长。
  • 容错性Base64编码可以容忍少量数据损坏或错误,因为每个字符只包含一部分原始数据。

使用场景

  • 数据传输Base64编码常用于在网络上传输二进制数据,如在邮件附件、图像、音频等数据的传输中。
  • 数据存储Base64编码可以将二进制数据以文本形式存储在文件或数据库中,方便存储和读取。
  • 加密算法:在一些加密算法中,Base64编码被用于表示密钥和签名等数据。

示例代码

  • Java
java
String 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);
  • Go
go
str := "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类型模式和无填充模式。这些模式在编码过程中会对字符集、填充方式等进行调整,以适应不同的使用场景。以下是它们之间的区别:

  • URL安全模式:在标准的Base64编码中,使用字符+/分别表示6263,这些字符在URL中有特殊含义,需要进行转义。因此,URL安全模式将+/替换为-_,以避免在URL中引起歧义。常用于在URL中传输Base64编码的数据,以避免URL转义和解码的问题。
  • MIME类型模式MIME类型模式在标准的Base64编码基础上,使用字符+/表示 6263,但是会在编码后的字符串末尾添加一个或两个=作为填充字符。常用于表示 MIME类型的数据,如邮件附件等。
  • 无填充模式:无填充模式在标准的Base64编码中不使用填充字符=。这意味着编码后的字符串长度可能不是4的倍数。在一些特定场景下,需要节省空间或者不需要严格的长度要求时可以使用无填充模式。

这些变体模式在编码和解码过程中的基本原理与标准的Base64编码相同,只是在字符集和填充方式上有所不同,以适应不同的使用场景。

3.5 十六进制编码

十六进制编码是一种将数据转换为十六进制表示的编码方式。它将数据中的每个字节转换为两位十六进制数字表示,以便于传输和存储。在十六进制编码中,每个字节的高四位和低四位分别转换为对应的十六进制数字,例如,字节值0xAB被表示为AB

特点

  • 可打印性:十六进制编码生成的字符串只包含十六进制数字和可能的分隔符,适用于将二进制数据表示为文本格式。
  • 数据密度低:每个字节转换为两位十六进制数字,因此编码后的字符串长度是原始数据长度的两倍。
  • 简单直观:十六进制编码使用简单直观的表示方式,易于人类阅读和理解。

使用场景

  • 调试和日志记录:十六进制编码常用于调试和日志记录中,以便查看和分析二进制数据。
  • 网络传输:在一些网络协议中,如·HTTP·、·TCP/IP·,十六进制编码可用于传输二进制数据。
  • 数据存储:十六进制编码可以将二进制数据以文本形式存储在文件或数据库中,方便存储和读取。

示例代码

  • Java
java
private 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; }
  • Go
go
str := "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

第四部分 摘要运算算法(哈希算法)

哈希算法,也称为散列函数或摘要算法,是一种将任意长度的输入数据(消息)映射为固定长度输出值(哈希值或摘要)的数学函数。哈希值通常较小,具有以下特性:输入敏感性(即使是微小的输入变化也会导致哈希值的巨大不同)、不可逆性(从哈希值很难或几乎不可能复原原始输入数据)、确定性(相同输入总是产生相同输出)、高效性(计算速度快)。哈希算法在设计上应尽量避免冲突,即不同的输入产生相同的输出(哈希碰撞),但实际上完全避免碰撞在数学上是不可能的,只能尽量降低其发生的概率。

使用场景

  • 数据完整性验证:通过比较文件的哈希值来检测数据是否被篡改。
  • 密码存储:存储密码的哈希值而非明文,提高安全性。
  • 数字签名:与非对称加密结合使用,验证数据的完整性和来源。
  • 数据索引:快速检索数据库中的记录。
  • 负载均衡:在分布式系统中,使用哈希值将请求分配到不同的服务器。
  • 唯一性校验:确保数据的唯一性,如通过哈希值检查重复的记录。

解决的问题

  • 快速验证:快速检测数据是否被篡改。
  • 安全性:在不暴露原始数据的情况下,验证数据的完整性。
  • 存储效率:使用固定长度的哈希值代替可变长度的原始数据,节省存储空间。
  • 分布式系统的一致性:通过哈希算法实现数据的均匀分布。

代表性算法

  • MD5(Message Digest Algorithm 5):曾广泛使用,但因为发现多种哈希碰撞而被认为不安全,不再推荐用于安全敏感的应用。
  • SHA-1(Secure Hash Algorithm 1):被广泛使用,但后来也发现了碰撞,因此不再被认为是安全的。
  • SHA-2(Secure Hash Algorithm 2):包括SHA-256SHA-512等,是目前广泛使用的一系列安全哈希算法。
  • SHA-3(Secure Hash Algorithm 3):是SHA-2的后继者,基于不同的数学基础,旨在提供更长远的安全性。
  • BLAKE2:是一个快速、安全的哈希算法,设计用于替代旧的SHA-2算法。
  • RIPEMD(RACE Integrity Primitives Evaluation Message Digest):一系列哈希函数,包括RIPEMD-160,曾被用在某些加密货币中。

4.1 MD5

MD5Message Digest Algorithm 5)是一种常用的哈希算法,用于产生消息摘要。它将任意长度的消息作为输入,经过一系列操作生成固定长度(128比特或16字节)的哈希值。MD5算法的核心原理是将输入的消息按照一定的规则分成若干个块,并对每个块进行一系列的位操作和模运算,最终生成一个128比特(16字节)的哈希值。MD5算法的设计是为了保证输入消息的微小变化会导致输出哈希值的大幅度变化,从而提高了哈希值的唯一性。

特点

  • 固定长度输出MD5算法生成的哈希值固定长度为128比特,无论输入消息的长度如何。
  • 不可逆性MD5算法是单向哈希算法,不可逆,即无法从哈希值反推出原始消息。
  • 高效性MD5算法在计算哈希值时非常高效,适用于对大量数据进行哈希计算。
  • 碰撞概率:由于哈希值的固定长度和输入消息的无限长度,MD5算法存在碰撞(两个不同的消息产生相同的哈希值)的概率。

使用场景

  • 数据完整性校验MD5常用于校验文件完整性,通过比对文件的MD5哈希值来验证文件是否被篡改。
  • 密码存储MD5可以用于存储用户密码的哈希值,以增加密码的安全性。
  • 数字签名MD5可以用于生成消息摘要,用于数字签名和身份验证等场景。

示例代码

  • Java
java
String 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)));
  • Go
go
str := "jianggujin" encoded := md5.Sum([]byte(str)) fmt.Println("MD5 hash:", hex.EncodeToString(encoded[:]))

运行并观察控制台输出:

MD5 hash: acab4efdfd3b8efcdec37fe160d7be0e

4.2 SHA

SHA(安全哈希算法)是一组密码哈希函数,用于产生消息的哈希值。SHA算法家族包括了多个版本,如SHA-1SHA-256SHA-512等,它们都是在输入数据上执行相似操作但输出长度不同的哈希函数。SHA算法基于密集型的位操作、逻辑运算和模运算。它将输入数据按照一定的方式分块,然后对每个块进行一系列的位运算和置换,最终产生一个固定长度的哈希值。

特点

  • 固定长度输出:不同版本的SHA算法生成的哈希值长度不同,但每个版本的输出长度是固定的。
  • 不可逆性SHA算法是单向哈希函数,不可逆,即无法从哈希值反推出原始消息。
  • 抗碰撞能力SHA算法在设计上具有很高的碰撞概率,即使输入数据发生微小变化,输出哈希值也会有显著差异。
  • 安全性SHA算法是一种被广泛使用且安全可靠的哈希算法,通常用于数字签名、消息完整性校验等安全领域。

使用场景

  • 数字签名SHA算法常用于生成数字签名,用于验证数据的完整性和真实性。
  • 密码存储SHA算法可用于存储用户密码的哈希值,增加密码的安全性。
  • 消息认证SHA算法可用于消息认证码(HMAC)的计算,用于验证消息的真实性和完整性。
  • 数据完整性校验SHA算法可用于校验文件完整性,如下载文件的哈希值与预期值进行比对。

示例代码

  • Java
java
String 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)));
  • Go
go
str := "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

4.3 SM3

SM3是中国国家密码管理局(中国密码学会)制定的一种密码哈希算法,用于产生消息的哈希值。它是中国政府采用的国家密码算法标准之一,具有自主知识产权。SM3算法基于分组密码结构,采用了类似于SHA-256的分组运算、置换运算和轮函数等步骤,但在设计上有一些独特之处。它将消息按照一定的规则分组,然后对每个分组进行一系列的位运算和置换操作,最终生成一个256位(32字节)的哈希值。

特点

  • 固定长度输出SM3算法生成的哈希值长度固定为256比特(32字节)。
  • 抗碰撞能力SM3算法在设计上考虑了碰撞攻击,具有很高的抗碰撞能力。
  • 国家密码算法标准SM3是中国政府采用的国家密码算法标准,具有自主知识产权,被广泛应用于政府和企业信息安全领域。
  • 性能高效SM3算法在计算哈希值时性能高效,适用于大规模数据的哈希计算。

使用场景

  • 数据完整性校验SM3算法可用于校验文件完整性,验证数据在传输过程中是否被篡改。
  • 数字签名SM3算法可用于生成数字签名,用于验证数据的完整性和真实性。
  • 密码学应用SM3算法可用于密码学应用中的哈希计算和消息认证等场景。

示例代码

  • Java
java
Security.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>
  • Go
go
str := "jianggujin" encoded := sm3.Sm3Sum([]byte(str)) fmt.Println("SM3 hash:", hex.EncodeToString(encoded))

Go默认未提供SM3实现,示例代码依赖github.com/tjfoc/gmsm/sm3

运行并观察控制台输出:

SM3 hash: 26b306b040c8b1a31728baf37ac7fb88285356eb20f762a0e2ae9e00ff338dad

第五部分 消息认证码(Message Authentication Code,MAC)

消息认证码通常使用对称密钥加密算法,如HMAC(基于哈希的消息认证码)或CMACCipher-based MAC)。消息认证码通常用于保护数据的完整性,防止篡改和伪造攻击。它可以确保消息在传输过程中没有被篡改或伪造,并且只有知道密钥的合法用户才能够验证消息的真实性。

使用场景

  • 数据传输安全:在网络通信中,消息认证码用于保护数据的完整性和真实性,防止数据被篡改或伪造。
  • 身份验证:在身份验证过程中,消息认证码用于验证用户的身份信息的真实性,防止身份信息被伪造或篡改。
  • 数字签名:在数字签名过程中,消息认证码用于生成数字签名,验证签名者的身份并保护签名的完整性和真实性。
  • 存储安全:在存储过程中,消息认证码用于保护数据的完整性和真实性,防止数据在存储过程中被篡改或损坏。

解决的问题

  • 数据完整性MAC可以确保数据在传输过程中没有被篡改或损坏。接收方可以使用相同的密钥和MAC算法对接收到的消息计算出一个认证标签,如果接收到的消息的认证标签与计算出的认证标签不一致,说明消息可能已经被篡改,从而保证了数据的完整性。
  • 数据真实性MAC可以验证消息的真实性,确保消息来自于合法的发送方,并且没有被伪造。只有知道密钥的合法用户才能够计算出正确的MAC,从而验证消息的真实性。
  • 防止重放攻击MAC通常与时间戳或随机数结合使用,以防止重放攻击。通过在消息中包含时间戳或随机数,并将其作为输入计算MAC,可以确保每个消息都是唯一的,防止攻击者重放之前的消息来欺骗系统。
  • 身份验证MAC可以用于验证用户的身份信息的真实性,确保用户具有相应的权限和权限。

代表性算法

  • HMAC(Hash-based Message Authentication Code):基于哈希函数的消息认证码算法,结合了哈希函数和密钥进行加密运算,生成认证标签。
  • CMAC(Cipher-based Message Authentication Code):基于对称加密算法的消息认证码算法,使用块密码和密钥对消息进行加密运算,生成认证标签。
  • Poly1305:一种快速的消息认证码算法,常与加密算法如ChaCha20结合使用,用于实现高性能的加密和认证。

5.1 HMAC

HMAC(基于哈希的消息认证码)是一种用于验证消息完整性和真实性的密码学算法。它结合了哈希函数和密钥,用于生成一个固定长度的认证码,以确保消息的完整性和真实性。HMAC算法基于哈希函数和密钥的组合。它将消息和密钥作为输入,通过一系列的哈希计算生成一个固定长度的认证码。在计算过程中,消息被与一个内部的固定值(称为ipad)进行异或操作,然后再与另一个内部固定值(称为opad)进行连接,并且再次进行哈希计算,最终生成认证码。

特点

  • 数据完整性HMAC可以验证消息的完整性,确保消息在传输过程中没有被篡改。
  • 密钥认证HMAC使用密钥来生成认证码,只有持有正确密钥的一方才能验证消息的真实性。
  • 抗碰撞能力HMAC基于哈希函数,具有很高的抗碰撞能力,即使输入消息发生微小变化,也会导致认证码的变化。
  • 灵活性HMAC可以使用多种哈希函数和不同长度的密钥,具有一定的灵活性和可配置性。

使用场景

  • 消息认证HMAC用于验证消息的真实性和完整性,防止消息被篡改或伪造。
  • 身份验证HMAC可以用于身份验证系统中生成和验证令牌,确保只有持有正确密钥的用户才能访问受保护的资源。
  • 数字签名HMAC可以用于生成数字签名,用于证明数据的来源和完整性。
  • API认证HMAC可以用于API认证中,确保只有经过授权的客户端才能访问API

示例代码

  • Java
java
byte[] 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);
  • Go
go
key := []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值。

摘要运算(哈希函数)

  • 目的:哈希函数主要用于提供数据的完整性校验。它将任意大小的数据映射为固定长度的输出(哈希值),任何对原始数据的修改都会导致哈希值的显著变化。
  • 密钥使用:标准的哈希函数(如MD5SHA-1SHA-256)在计算时不涉及密钥,是无密钥的函数。
  • 安全性属性:哈希函数主要关注抗碰撞性和非逆性,用于检测数据是否有变动,但不能提供认证,因为任何人都可以对任意数据计算哈希值。

MAC是带有密钥的,提供消息认证和完整性检查,适合于需要验证消息来源的场景。哈希函数是无密钥的,仅提供完整性校验,不涉及认证,适用于不需要识别发送方身份的情景,如文件完整性验证。两者虽都能检验数据完整性,但MAC通过密钥的加入,额外提供了认证功能,这是它们之间最本质的区别。

第六部分 对称加密算法

对称加密算法是一种加密和解密过程使用相同密钥的加密技术。这意味着发送方和接收方必须事先共享同一把密钥,并且在保密的前提下保存好这把密钥。加密时,发送方使用该密钥将明文(即未加密的信息)转换为密文(即加密后的信息);解密时,接收方再利用相同的密钥将密文还原回明文。由于加解密使用的是相同的密钥,这类算法也被称作“秘密密钥算法”或“共享密钥算法”。

使用场景

  • 数据加密传输:在网络通信中,对称加密算法常用于加密大量数据,如文件传输、视频流或数据库通信,因为它处理速度快,效率高。
  • 存储数据加密:对敏感数据进行存储时,可以使用对称加密保证数据在静止状态下的安全。
  • 内部系统通信:企业内部系统间的数据交换,尤其是在信任环境中,对称加密可以简化密钥管理并提高效率。
  • 临时会话加密:在需要快速建立加密通道的场景,如即时通讯应用中,双方可以快速协商并使用一个临时的对称密钥进行通信。

解决的问题

  • 数据机密性:防止未授权的第三方读取敏感信息。
  • 数据完整性:确保数据在传输或存储过程中未被篡改。
  • 效率:对称加密算法通常计算效率高,适合实时或大批量加密需求。

代表性算法

  • AES(高级加密标准):是目前最广泛使用的对称加密算法之一,以128位的块大小和多种密钥长度(128192256位)为特点。
  • DES(数据加密标准):曾经广泛使用,但由于其密钥长度较短(56位有效密钥长度),已不再被认为是安全的。
  • 3DES(三重数据加密算法):是DES的一个改进,使用三个密钥对数据进行三次加密,以提高安全性。
  • Blowfish:由Bruce Schneier设计,是一个快速、高效的对称加密算法,适用于多种加密需求。
  • Twofish:是Blowfish算法的后继者,提供更强的安全性和更大的密钥长度。
  • ChaCha20:是一个相对较新的流加密算法,以其快速和安全性而受到关注。
  • Salsa20ChaCha20的前身,也是一个流加密算法,以其简单和高效而闻名。

6.1 异或加密

异或加密是一种简单而古老的加密技术,也称为异或运算加密。它使用异或运算对明文和密钥进行位级别的操作,以生成密文。异或加密的原理非常简单,就是对明文和密钥的每一个比特进行异或运算。异或运算的规则是:如果两个比特相同,则结果为0;如果两个比特不同,则结果为 1。因此,当明文和密钥进行异或运算时,可以得到密文。

特点

  • 简单性:异或加密算法非常简单,只涉及到位操作,易于实现和理解。
  • 单次密钥:异或加密算法使用单次密钥,同一个密钥只能用于加密一次消息。
  • 对称性:异或加密算法是对称加密算法,加密和解密使用相同的密钥。
  • 不安全性:异或加密算法容易受到攻击,特别是在密文和明文之间有较强的相关性时。

使用场景

  • 教学演示:异或加密常用于教学演示中,帮助学生理解位操作和对称加密的基本原理。
  • 简单加密需求:在一些低安全性要求的场景下,异或加密可以用于简单消息的加密和解密。

示例代码

  • Java
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; }
  • Go
go
func 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

6.2 AES

AESAdvanced Encryption Standard,高级加密标准)是一种对称密钥加密算法,被广泛应用于保护敏感数据的安全性。它是美国联邦政府采用的加密标准,也是目前最常用的对称加密算法之一。AES加密算法采用分组密码结构,将明文按照固定长度(128192256 位)进行分组,然后通过一系列的轮函数和密钥迭代,对每个分组进行加密处理,最终生成密文。AES共有10轮、12轮或14轮轮函数,取决于密钥长度(128192256 位)。

特点

  • 安全性高AES加密算法采用了先进的分组密码结构和多轮加密过程,具有较高的安全性,抵抗常见的密码攻击。
  • 高效性AES加密算法在硬件和软件上都有高效的实现,加密和解密速度快,适用于大规模数据的加密处理。
  • 灵活性AES支持多种密钥长度(128192256位),具有一定的灵活性和可配置性。
  • 标准化AES是美国联邦政府采用的加密标准,被广泛应用于政府、企业和个人的信息安全领域。

使用场景

  • 数据加密AES可用于保护敏感数据的机密性,如用户密码、支付信息、个人身份信息等。
  • 通信加密AES可用于保护网络通信的安全性,如HTTPSSSL/TLS等协议中的数据加密。
  • 存储加密AES可用于加密存储介质中的数据,如硬盘、数据库、云存储等。
  • 数字签名AES可用于生成数字签名,用于验证数据的完整性和真实性。

在使用AES加密算法时,除了指定密钥长度外,还需要指定工作模式(Mode of Operation)和填充模式(Padding Scheme)。

工作模式(Mode of Operation)

工作模式定义了对待加密的数据块的方式,常见的工作模式包括:

  • ECB(Electronic Codebook):电子密码本模式,将每个明文块分别加密,适用于独立且不重复的数据块。ECB模式不提供数据的机密性和完整性保护。
  • CBC(Cipher Block Chaining):密文分组链接模式,每个明文块与前一个密文块进行异或操作后再加密,提供更好的数据保护性。
  • CFB(Cipher Feedback):密文反馈模式,将前一个密文块用作加密器的输入,适用于流式数据加密。
  • OFB(Output Feedback):输出反馈模式,类似于CFB模式,但是输出的是密钥流而不是密文块。
  • CTR(Counter):计数器模式,将初始向量(IV)与计数器相结合产生密钥流,然后将明文与密钥流进行异或操作来加密数据。

填充模式(Padding Scheme)

填充模式用于处理明文块的长度与加密算法要求的数据块长度不匹配的情况。常见的填充模式包括:

  • NoPadding:不添加填充,要求明文长度必须是加密算法要求的数据块长度的整数倍,否则会出错。
  • PKCS5Padding/PKCS7Padding:采用PKCS#5PKCS#7标准的填充方案,将填充字节的值设置为填充的长度,解密时可通过填充长度来识别和去除填充。

相关信息

PKCS5PaddingPKCS7Padding都是填充方案,用于在加密过程中对明文进行填充,以满足加密算法要求的数据块长度的整数倍。

主要区别

  • 填充字节的值
    • PKCS5Padding:填充的字节值固定为填充的长度。
    • PKCS7Padding:填充的字节值等于填充的长度。
  • 适用范围
    • PKCS5Padding:规定了填充字节的长度必须为18字节之间(包含),因此适用于块长度为64位(8字节)的加密算法。
    • PKCS7Padding:没有对填充字节的长度做出限制,适用于块长度为1255字节之间的加密算法。

总体来说,PKCS7PaddingPKCS5Padding的一个超集,它可以处理更广泛的块长度范围。在实践中,PKCS7Padding更常见,并且在大多数情况下,PKCS7Padding也适用于 PKCS5Padding所适用的情况。

示例代码(AES/CBC/PKCS5Padding

  • Java
java
byte[] 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));
  • Go
go
func 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

6.3 DES

DESData Encryption Standard,数据加密标准)是一种对称密钥加密算法,是最早广泛应用的分组密码算法之一。DES使用64位密钥对64位的数据块进行加密和解密,经过多轮的置换和替换操作,生成密文。DES加密算法采用分组密码结构,将明文按照固定长度(64 位)进行分组,然后通过一系列的置换、替换和轮函数,对每个分组进行加密处理,最终生成密文。DES共有16轮轮函数,每轮使用不同的子密钥对数据进行处理。

特点

  • 安全性DES曾经是加密领域的标准,但随着计算能力的增强,现已不再安全,易受到穷举搜索等攻击。
  • 固定密钥长度DES的密钥长度固定为64位,其中8位用作奇偶校验,实际有效密钥长度为56位。
  • 单一加密算法DES只提供加密功能,没有提供完整性验证或认证功能。
  • 性能高DES的加密和解密速度相对较快,适用于对称加密领域。

使用场景

  • 历史遗留系统:一些旧版系统或遗留系统可能仍在使用DES加密算法,需要维护和兼容。
  • 教学和研究DES曾经是密码学领域的研究热点,对于学习密码学的学生和研究人员具有一定的参考意义。
  • 低安全性需求:在一些对安全性要求不高的场景下,仍可以使用DES进行数据加密,如某些内部通信或测试系统。

工作模式与填充模式参见AES部分内容.

示例代码(DES/CBC/PKCS5Padding

  • Java
java
byte[] 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));
  • Go
go
key := []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

6.4 3DES

3DESTriple Data Encryption Standard)是对DES加密算法的改进版本,通过对数据应用三次DES加密来提高安全性。它采用了三个不同的密钥对数据进行加密和解密,从而增强了加密的强度和安全性。在3DES中包含了两种不同的加密模式:双倍长密钥模式(Two-Key Triple DES)和三倍长密钥模式(Three-Key Triple DES)。

  • 双倍长密钥模式:在双倍长密钥模式下,使用两个56位的密钥,分别作为第一轮和第三轮的密钥,而第二轮的密钥与第一轮相同。
  • 三倍长密钥模式:在三倍长密钥模式下,使用三个56位的密钥,分别作为三轮的密钥。

特点

  • 提高安全性3DES使用了三次DES加密,比单次DES加密更加安全,对抗穷举搜索攻击的能力更强。
  • 兼容性3DES兼容DES加密算法,可以在不修改现有系统的情况下进行升级。
  • 延续性3DESDES的直接后继者,保留了DES的许多特点和优点,同时增加了更高的安全性。
  • 性能较低:由于需要执行三次DES加密和解密操作,3DES的性能较低,不适合在对性能要求较高的场景中使用。

使用场景

  • 遗留系统升级:对于仍在使用DES加密算法的遗留系统,可以通过将DES替换为 3DES 来提高安全性。
  • 金融领域:在金融领域,对安全性要求较高的应用场景中,如支付系统、银行交易等,常常会使用3DES加密算法来保护敏感数据。
  • 密码学研究3DES作为经典的加密算法,仍然具有一定的研究价值,用于密码学领域的学术研究和教学。

工作模式与填充模式参见AES部分内容.

示例代码(DESede/CBC/PKCS5Padding

  • Java
java
byte[] 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));
  • Go
go
key := []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

6.5 SM4

SM4是一种分组密码算法,也称为国密算法,由中国国家密码管理局设计并公开的。它是一种对称加密算法,适用于数据加密和解密,以及数据完整性验证等场景。SM4算法基于Feistel网络结构,具有32轮加密和解密过程。它采用了非线性变换、置换运算和密钥混合技术,通过对数据块的多次迭代处理,实现数据的加密和解密。

特点

  • 安全性高SM4采用了混沌运算和S盒代替传统的置换运算,增强了算法的非线性特性,提高了安全性。
  • 速度快SM4算法具有较高的运算速度,适用于大规模数据加密和解密的场景。
  • 标准化SM4算法已被ISO/IEC标准化,并被广泛应用于政府、金融、电信等领域。
  • 自主可控SM4算法是国家自主设计的密码算法,具有自主可控的特点。

使用场景

  • 政府和军事领域SM4算法是中国国家密码管理局发布的国家密码算法,适用于政府和军事领域的数据加密和解密。
  • 金融和电信领域SM4算法已被广泛应用于金融、电信等领域的数据加密和安全通信。
  • 云计算和物联网:随着云计算和物联网的发展,对数据安全的需求越来越高,SM4算法可以用于保护数据的安全性。

工作模式与填充模式参见AES部分内容.

示例代码(SM4/CBC/PKCS5Padding

  • Java
java
Security.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>
  • Go
go
key := []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

第七部分 非对称加密算法

非对称加密算法是一种加密和解密过程使用不同密钥的加密技术,它需要一对密钥:公开密钥(公钥)和私有密钥(私钥)。公钥可以公开给任何人,用于加密信息,而私钥必须保密,仅由信息的接收者持有,用于解密信息。这种机制解决了密钥分发和安全通信的难题,因为即使公钥广为人知,没有对应的私钥也无法解密信息。非对称加密算法的加密过程和解密过程是数学上相关的,但直接从公钥推算出私钥在计算上是不可行的,这确保了系统的安全性。

使用场景

  • 数据加密:发送方使用接收方的公钥加密信息,只有拥有相应私钥的接收方能解密,确保了信息的机密性。
  • 数字签名:发送方使用自己的私钥对信息进行签名,任何持有发送方公钥的人都能验证签名,确认信息的完整性和来源的真实性。
  • 密钥交换:在对称加密之前,双方可以安全地交换用于对称加密的会话密钥,避免了直接传递密钥的风险。
  • 身份验证:服务器或个人可以公开公钥作为身份标识,私钥则用于证明对该身份的所有权。
  • 区块链技术:在区块链中,非对称加密用于生成地址、进行交易签名,确保交易的安全性和不可篡改性。

解决的问题

  • 密钥分发:无需安全渠道事先共享密钥,降低了密钥管理的复杂度。
  • 安全性:即使公钥被广泛知晓,只要私钥保持安全,信息仍然是安全的。
  • 认证与不可否认性:数字签名机制确保信息发送者的身份和不可抵赖性。
  • 密钥协商:安全地协商会话密钥,为对称加密提供安全基础。

代表性算法

  • RSA:由Ron RivestAdi ShamirLeonard Adleman发明,是最早也是最知名的非对称加密算法之一,广泛应用于数字签名和密钥交换。
  • DSA(Digital Signature Algorithm):专为数字签名设计的算法,安全性基于离散对数问题。
  • Elliptic Curve Cryptography (ECC):基于椭圆曲线数学的算法,相比RSA,使用更短的密钥长度就能达到相同的安全级别,适用于资源受限的环境。
  • Diffie-Hellman (DH):一种密钥交换协议,允许双方在公开通信中生成共享的秘密密钥,而不需要事先共享任何秘密信息。
  • ElGamal:既可以用于加密也可以用于数字签名,基于离散对数问题,是DH密钥交换协议的扩展。

7.1 RSA

RSA是一种非对称加密算法,是目前应用最广泛的公钥加密算法之一。RSA算法基于数论中的大数分解问题,其安全性依赖于大整数分解的困难性。RSA算法基于两个大素数的乘积作为公钥的模数,公钥由模数和指数组成,私钥由模数和另一个指数组成。加密过程使用公钥对数据进行加密,解密过程使用私钥对密文进行解密。RSA算法的安全性基于大数分解问题的困难性,即给定一个大整数N,找到两个大素数pq,使得N = p * q,是一个困难的问题。

特点

  • 非对称加密RSA算法使用不同的密钥进行加密和解密,公钥用于加密,私钥用于解密,具有较高的安全性。
  • 数字签名RSA算法不仅可以用于加密数据,还可以用于生成和验证数字签名,确保数据的完整性和来源可信性。
  • 密钥长度灵活RSA算法支持不同长度的密钥,密钥越长,安全性越高,但加密和解密速度越慢。
  • 广泛应用RSA算法被广泛应用于安全通信、数字签名、身份认证等领域。

使用场景

  • 安全通信RSA算法可以用于保护通信中的数据隐私和完整性,例如SSL/TLSSSH 等安全协议。
  • 数字签名RSA算法可以用于生成和验证数字签名,确保数据的完整性和来源可信性,例如数字证书、电子邮件签名等。
  • 密钥交换RSA算法可以用于安全地交换对称加密算法的密钥,例如在Diffie-Hellman密钥交换中使用。
  • 身份认证RSA算法可以用于实现身份认证功能,例如在公钥基础设施(PKI)中用于用户身份验证。

示例代码(生成密钥)

  • Java
java
KeyPairGenerator 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()));
  • Go
go
key, _ := 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))

示例代码(加解密)

  • Java
java
X509EncodedKeySpec 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));
  • Go
go
str := "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))

示例代码(签名验签)

  • Java
java
String 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));
  • Go
go
str := "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)

相关信息

签名算法是一种用于确保数据完整性和认证数据来源的密码学技术。它通常与加密算法结合使用,用于生成数字签名,以便验证数据的真实性和完整性。下面是签名算法的详细介绍和说明:

基本原理

签名算法基于公钥加密和私钥解密的原理。发送方使用私钥对数据进行签名,接收方使用发送方的公钥来验证签名。如果数据在传输过程中被篡改,签名验证将失败,因为篡改后的数据与原始签名不匹配。

加密与签名的区别

虽然加密和签名都使用了公钥和私钥,但它们的目的和机制有所不同。加密是为了保护数据的隐私性,而签名是为了确保数据的完整性和认证数据来源。

签名的过程

  1. 发送方使用私钥对数据进行哈希计算,然后对哈希值进行签名生成数字签名。
  2. 发送方将原始数据和数字签名一起发送给接收方。

验证签名的过程

  1. 接收方使用发送方的公钥对原始数据进行哈希计算,得到哈希值。
  2. 接收方使用发送方的公钥和数字签名对哈希值进行解密,得到原始的哈希值。
  3. 接收方比较计算得到的哈希值与解密得到的哈希值是否一致。如果一致,则验证通过,否则验证失败。

7.2 SM2

SM2是一种基于椭圆曲线密码学(ECC)的国密算法,由中国密码学家提出,用于数字签名、密钥交换、公钥加密等场景。SM2算法是中国政府采用的国家密码算法标准之一。SM2算法基于椭圆曲线离散对数问题,其核心原理是在椭圆曲线上进行点运算,实现数字签名、密钥交换等功能。SM2算法采用了一套基于国际标准的椭圆曲线参数,具有较高的安全性和效率。

特点

  • 国家标准SM2算法是中国政府采用的国家密码算法标准,具有较高的安全性和可信度。
  • 椭圆曲线密码学SM2算法基于椭圆曲线密码学,具有较高的安全性和效率。
  • 数字签名SM2算法支持数字签名功能,可以用于数据认证、身份认证等场景。
  • 密钥交换SM2算法支持密钥交换功能,可以安全地交换密钥,用于加密通信。
  • 高效性SM2算法具有较高的运算效率,适用于大规模数据的加密和解密。

使用场景

  • 安全通信SM2算法可以用于安全通信场景,如SSL/TLS协议中的数字签名和密钥交换过程。
  • 数字签名SM2算法可以用于数字签名场景,如电子合同签署、电子证书认证等。
  • 密钥交换SM2算法可以用于密钥交换场景,如密钥协商、密钥派生等。

SM2算法的公钥和私钥有多种格式类型,每种格式都有其特定的用途和特点。常用格式与说明如下:

私钥格式

  • EC格式:私钥通常表示为一个随机生成的整数,这个整数通常用十六进制表示,并且是椭圆曲线上的一个点的倍数。
  • PKCS#1格式:私钥也可以表示为PKCS#1格式,这是一种较为传统的RSA私钥格式,但也可以用在ECC上。
  • PKCS#8格式PKCS#8是一种更通用的私钥表示方式,它包含了私钥以及一些额外的信息,如算法标识符。

公钥格式

  • 未压缩公钥:通常以前缀04开头,后跟两个256位数字;一个用于点的x坐标,另一个用于点的y坐标。
  • 压缩公钥:可能会省略前缀04,只包含xy坐标的值。
  • ASN.1 DER编码:公钥使用ASN.1 DER编码格式表示,这种格式在证书和某些加密系统中广泛使用。
  • PKCS#8格式:类似于私钥的PKCS#8格式,公钥也可以使用PKCS#8格式,它同样包含了公钥以及一些算法参数和标识符。

相关信息

在讨论SM2算法时,经常会提到的D值Q值(压缩与未压缩)Q值xQ值y,这些术语与椭圆曲线密码学(ECC)中的点表示和密钥生成过程相关。基本解释如下:

  • D值:在椭圆曲线密码学中,D值通常指的是私钥。它是用户选择的一个随机大整数,用来与椭圆曲线上的一点进行点乘运算,从而得到对应的公钥点。在SM2算法中,D值是私钥的表示,用于签名和解密操作。示例:761cdbbaeb1203adbf2afce9777ff6c247c8f30454055329d518bb429f330b99

  • Q值(压缩与未压缩)Q值通常指的是公钥,在椭圆曲线密码学中,公钥是由椭圆曲线上的一点表示的。这个点可以以两种形式存储或传输:

    • 未压缩格式:直接包含点的x坐标y坐标,通常会有额外的字节来标识是哪个坐标在哪个半平面(通常是通过一个额外的字节,如0x04,后面跟着xy坐标)。示例:04641B729BB329D780B853DA48B9755015B6C7ABE21EF9B61DF4D855BC1CBE41114C34776539182D0DFA6CA002959C72F2B6BE1B05DE5DD1A41C47F650DB780BB9
    • 压缩格式:仅包含x坐标,并且根据y坐标的奇偶性添加一个额外的字节(0x02表示y是偶数,0x03表示y是奇数)。这样可以节省存储空间,尤其是在带宽敏感的应用中。示例:03641B729BB329D780B853DA48B9755015B6C7ABE21EF9B61DF4D855BC1CBE4111
  • Q值x:指的是公钥点在压缩或未压缩格式中明确表示的x坐标。它是椭圆曲线上点的横坐标部分。示例:641B729BB329D780B853DA48B9755015B6C7ABE21EF9B61DF4D855BC1CBE4111

  • Q值y:指的是公钥点的y坐标。在未压缩格式的公钥表示中会明确包括y坐标,而在压缩格式中,y坐标不直接给出,但可以通过x坐标和椭圆曲线方程计算得到。示例:4C34776539182D0DFA6CA002959C72F2B6BE1B05DE5DD1A41C47F650DB780BB9

总的来说,D值是私钥,Q值代表公钥,而Q值xQ值y分别是公钥点在坐标平面上的x坐标y坐标,它们根据压缩与否的不同格式,可能以不同的方式表示或传输。

加密数据格式

  • C1C3C2格式SM2加密后的数据通常包含三个部分:随机产生的公钥C1、密文C2和使用SM3算法计算得到的消息摘要C3
  • ASN.1格式:加密数据也可以使用ASN.1格式表示,这在某些硬件加密机中使用。

相关信息

SM2加密数据最初使用C1C2C3格式数据,在新版标准中为C1C3C2格式,在实际使用中需要注意区分并在必要时进行数据转换。Java中常用的bouncycastle默认使用的格式为C1C2C3

签名数据格式

  • R|S格式SM2签名通常由两个部分组成,R和S,它们直接拼接在一起表示。
  • ASN.1格式:签名数据也可以使用ASN.1格式定义,这符合国标(GM/T 0009-2012)规定。

示例代码(生成密钥)

  • Java
java
Security.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>
  • Go
go
key, _ := 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

示例代码(逆向公私钥)

java
X9ECParameters 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());

示例代码(加解密)

java
PublicKey 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));

示例代码(签名验签)

java
String 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)

java
private 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)

java
private 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)); } }

第八部分 其他常用算法说明

8.1 CRC(循环冗余校验)算法

CRC(循环冗余校验)算法主要用于数据通信领域中的错误检测,它是一种广泛应用于数字网络和存储设备中的差错控制方法。

CRC算法的分类

CRC算法并没有严格的分类体系,但根据生成多项式的不同,可以有多种不同的CRC算法,常见的包括:

  • CRC-8: 使用8位生成多项式,如CRC-8-ATM
  • CRC-16: 使用16位生成多项式,如CRC-16-CCITTCRC-16-ANSI等。
  • CRC-32: 使用32位生成多项式,是通信领域中最常用的,如CRC-32-IEEE 802.3
  • CRC-64: 使用64位生成多项式,适用于需要更高检测能力的场合。

CRC算法解决的问题

CRC算法主要解决的是数据传输过程中的比特错误问题,通过计算数据流与特定生成多项式之间的模二除法余数,生成一个校验码附加到数据后面。接收端重新计算CRC并与接收到的CRC校验码比较,如果一致,则认为数据在传输过程中没有发生错误或者发生了可检测的错误。

与信息摘要的异同

  • 相同点CRC算法和信息摘要算法(如MD5SHA系列)都属于散列函数的范畴,它们都接受输入数据并产生固定长度的输出,且输出对输入非常敏感,即使输入数据有微小变化,输出也会显著不同。
  • 不同点
    • 目的不同CRC主要用于错误检测,侧重于检测数据在传输或存储过程中的随机错误;而信息摘要算法主要用于数据完整性和身份验证,侧重于防止数据被篡改或确保数据来源的可靠性。
    • 安全性:信息摘要算法设计上考虑了抗碰撞性,即找到两个不同输入具有相同摘要的难度极大,因此在安全性要求高的场景下更适用;而CRC算法在设计上并未考虑抗碰撞性,容易受到精心构造的攻击。
    • 输出长度和复杂性:信息摘要算法的输出长度通常更长(如128位、256位等),且算法更为复杂,适用于安全认证;CRC的输出较短(如8位、16位、32位等),计算相对简单,适用于快速错误检测。

与消息认证算法的异同

  • 相同点:两者都涉及数据完整性验证,可以用于检测数据是否被篡改。
  • 不同点
    • 应用场景:消息认证算法(如HMACCMAC)除了验证数据完整性外,还常用于提供消息认证服务,确保数据来源的真实性,适用于安全通信。而CRC主要解决物理层或链路层的数据传输错误,不直接提供安全认证功能。
    • 安全性级别:消息认证算法设计上更加注重安全性,能够抵抗恶意篡改和选择明文攻击,通常结合密钥使用,而CRC算法不依赖密钥,安全性较低。
    • 计算复杂度:消息认证算法一般比CRC算法复杂,计算成本较高,但在需要高度安全的环境下是必要的。

示例代码

  • Java
java
String str = "jianggujin"; CRC32 crc32 = new CRC32(); crc32.update(str.getBytes(StandardCharsets.UTF_8)); System.out.println("CRC32 value: " + crc32.getValue());
  • Go
go
str := "jianggujin" value := crc32.ChecksumIEEE([]byte(str)) fmt.Println("CRC32 value:", value)

运行并观察控制台输出:

CRC32 value: 724585211

8.2 SM9基于身份的密码

SM9算法属于基于身份的密码。基于身份的密码是一种“高级”的公钥密码方案,在具备常规公钥密码加密、签名等密码功能的同时,基于身份的密码体系不需要CA中心和数字证书体系。SM9方案的基本原理是,可以由用户的唯一身份ID(如对方的电子邮件地址、域名或ID号等),从系统的全局主密钥中导出对应的私钥或公钥,导出密钥的正确性是由算法保证的,因此在进行加密、验签的时候,只需要获得解密方或签名方的ID即可,不再需要对方的数字证书了。因此如果应用面对的是一个内部的封闭环境,所有参与用户都是系统内用户,那么采用SM9方案而不是SM2证书和CA的方案,可以简化系统的开发、设计和使用,并降低后续CA体系的维护成本。

对应数字证书体系中的CA中心,SM9体系中也存在一个权威中心,用于生成全局的主密钥(MasterKey),并且为系统中的每个用户生成、分配用户的私钥。和SM2密钥对一样,SM9的主密钥也包含私钥和公钥,其中主公钥(PublicMasterKey)是可以导出并公开给系统中全体用户的。而SM9中用户的密钥对比较特殊,其中的公钥并不能从私钥中导出,SM9用户密钥需要包含用户的ID起到公钥的作用,在加密和验证签名等密码计算中,真正的用户公钥是在计算中,在运行时通过用户ID从主公钥中导出的。因此从应用的角度看,SM9中用户的公钥就是一个字符串形式的ID

第九部分 API常用保护措施

9.1 幂等(Idempotency)

幂等性是指一个操作或接口,无论执行多少次,其结果都是一样的,系统的状态不会因为重复执行而发生改变。换句话说,多次请求的效果与单次请求相同,这对于确保数据一致性特别重要,尤其是在网络不稳定或用户重复提交请求时。

使用场景

  • 支付系统: 确保支付操作不会因为网络延迟或用户误操作而被重复处理。
  • 订单创建、更新: 避免订单状态因多次请求而错误更改。
  • 数据插入或更新操作: 在数据库操作中,确保即使请求多次发送,数据也不会被多次插入或修改。

实现方式

  • 使用唯一标识(Transaction ID): 每个请求附带一个唯一的标识符,服务端根据该标识判断请求是否已经处理过。
  • 乐观锁机制: 在数据库记录中增加版本号字段,每次更新时检查版本号是否匹配,确保操作的唯一性。
  • 状态机模式: 设计业务流程为一系列不可逆的状态变更,避免重复执行同一状态变更操作。
  • 缓存机制: 利用缓存存储最近的处理结果,对于相同的请求直接返回缓存结果。

9.2 防篡改(Tamper Resistance)

防篡改旨在保护数据在传输过程中不被未经授权的第三方修改。这通常通过数据签名、消息认证码(MAC)、数字签名或加密来实现,确保数据的完整性和来源的真实性。

使用场景

  • 敏感数据传输: 如银行交易、医疗记录传输,确保信息在传输过程中未被篡改。
  • 软件更新: 确保软件更新包的完整性,防止恶意软件注入。
  • 法律文档电子签名: 保证电子文件的内容自签署后未发生变化。

实现方式

  • 数据加密: 对敏感数据进行加密传输,如使用HTTPS协议,确保数据在传输过程中不被查看或修改。
  • 数字签名: 使用私钥对数据进行签名,接收方通过公钥验证签名,确认数据来源和完整性。
  • 消息认证码(MAC)或HMAC: 计算请求内容的MAC值,服务端通过共享密钥验证MAC,确保数据未被篡改。
  • TLS/SSL: 在网络层使用SSL/TLS协议,提供端到端的数据加密和完整性保护。

9.3 防重放(Replay Prevention)

防重放机制用于阻止攻击者捕获有效的数据包或请求然后重新发送,以欺骗系统执行非授权的操作。它通过添加时间戳、序列号、一次性令牌或使用加密技术来验证请求的新鲜度和合法性。

使用场景

  • 金融交易: 防止交易指令被恶意重播,造成资金重复转移。
  • 身份验证过程: 确保登录或授权请求不被恶意利用。
  • API通信: 在开放API调用中保护数据交互的安全性。

实现方式

  • 时间戳+签名: 请求中包含时间戳,并结合密钥计算签名,服务端校验时间戳的新鲜度及签名的有效性。
  • nonce(一次性令牌): 每个请求携带一个服务器生成的、仅使用一次的随机数,服务端验证并记录已使用的nonce,拒绝重复的nonce
  • 序列号: 在请求中加入递增的序列号,服务端验证序列号的连续性和唯一性。
  • 会话令牌定期刷新: 对于需要长时间连接的场景,定期更新会话令牌,旧令牌失效,减少重放攻击窗口。

9.4 防信息泄露(Information Leakage Prevention)

防信息泄露指的是采取措施防止敏感信息在未经许可的情况下被披露给未经授权的实体。这包括数据加密、访问控制、最小权限原则、安全审计等手段。

使用场景

  • 企业内部网络: 限制员工访问特定的敏感数据,比如财务报告、客户个人信息。
  • 云存储: 对存储在云端的数据进行加密,确保即使数据被盗也无法直接读取。
  • Web应用: 防止通过错误配置或漏洞暴露用户数据,如SQL注入、XSS攻击。

实现方式

  • 最小权限原则: 用户或系统组件只授予完成任务所需的最小权限,减少信息暴露风险。
  • 数据脱敏和匿名化: 对敏感信息进行处理,如隐藏部分数据或使用假名,特别是在日志记录和测试环境中。
  • 访问控制列表(ACL)和角色基础访问控制(RBAC): 精细化管理资源访问权限,确保只有授权用户可以访问敏感信息。
  • 加密存储: 对存储的敏感数据进行加密,即便数据被盗,也无法直接阅读。
  • 安全审计和监控: 定期审查系统日志,监测异常行为,及时发现潜在的信息泄露风险。
如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:蒋固金

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!