作者因为估计选不到老师的课,第三周就针对性的进行学习和实操,作者认为还是实操最重要。
数据库与Web技术加密模式
在数据库和Web技术中,加密是一个常用来保护数据和通讯的手段。以下是在这两个领域中可能使用到的加密模式:
-
密文分组链接模式 (CBC: Cipher Block Chaining Mode)
- 由于其增加了难以预测性,CBC模式通常用于数据库加密和某些Web应用程序中的数据加密。
- 在SSL/TLS(安全套接字层/传输层安全性)中,CBC模式曾经是推荐的加密模式,但由于存在某些攻击(如BEAST攻击),现在通常建议使用其他模式。
-
计数器模式 (CTR: Counter Mode)
- 由于其可以并行处理的特性,CTR模式在某些Web技术和大型数据库中是首选的加密模式。本文重点研究计数器模式。
- 在SSL/TLS中,有一个称为GCM(伽罗华计数模式)的变种,它结合了CTR模式和消息认证码,提供了加密和完整性保护。
-
电码本模式 (ECB: Electronic Codebook Mode)
- 由于其固有的安全性问题,ECB模式通常不建议在Web技术或数据库中使用。当使用相同的密钥加密时,相同的输入块会生成相同的输出块,这使得它容易受到分析攻击。
-
输出反馈模式 (OFB: Output Feedback Mode) 和 密文反馈模式 (CFB: Cipher Feedback Mode)
- 这两种模式在Web和数据库应用中较少使用,但在特定的应用场景中它们可能会出现。
-
流模式
- 当我们谈论流模式时,我们通常指的是像RC4这样的算法,它原生就是为流模式设计的。RC4曾经是Web加密(特别是在早期的SSL/TLS版本中)的主要算法,但由于已知的安全性问题,现在已经不再建议使用。
在选择加密模式时,总是建议使用现代、经过广泛审核和验证的模式和实现,避免使用已知有安全问题或已被弃用的模式。所以作者不学旧东西了,直接学习和作者工作稍微接轨的知识。
CBC&CTR
基础概念
在开始之前,我们需要明白以下几个点:
-
块密码: 这是一种对固定大小的明文块(例如128位)进行加密的方法。最著名的块密码算法是AES。
-
初始化向量 (IV): 这是一个与明文一起使用的随机值,用于加密的起始阶段。IV不需要保密,但通常每次加密都会更改。
密文分组链接模式 (CBC)
-
如何工作:
- 在开始加密第一个块之前,先将明文的第一个块与一个随机的初始化向量(IV)进行异或操作。
- 对结果使用块密码进行加密。
- 结果(即第一个加密块)用于与明文的下一个块进行异或操作,然后再次使用块密码进行加密。
- 这个过程重复,直到加密所有的明文块。
使用场景:例如,当你需要存储加密的敏感数据(如信用卡信息)时,CBC是一个可靠的选择,因为它提供了良好的安全性。
优点:相同的明文块和相同的密钥,由于IV的存在,在不同的加密过程中会产生不同的密文。
缺点:串行化: 由于每个块的加密都依赖于前一个块,所以CBC不太适合并行化处理。
计数器模式 (CTR)
-
如何工作:
- 给定计数器值。
- 使用AES-128和密钥K加密上一步的结果,但不与明文进行任何操作。
- 得到的输出与"HELLO"明文进行异或操作。
- 得到的结果即为密文,这就是该字符串在数据库中的加密版本。
- 对于下一个块(如果存在),增加计数器值,并重复上述步骤。
使用场景:在需要快速并行读/写的情境下,CTR模式是一个不错的选择,因为你可以独立地加密或解密任何块。
优点:
- 并行性: CTR模式支持并行加密/解密,因为每个块的处理不依赖于其他块。
- 随机访问: 适合于需要随机访问数据块的应用,例如数据库。
缺点:计数器的值必须为每个块保持独特,否则安全性会受到威胁。如果重新使用计数器和密钥的组合,攻击者可能会找出明文。
结合数据库
假设你有一个数据库,其中存储了用户的敏感信息。为了保护这些数据,你决定使用加密。
-
如果你使用CBC,每次加密一个数据行时,你都会生成一个新的IV。由于CBC的串行性,写入大量数据时可能会略显缓慢。
-
另一方面,如果你使用CTR,你可以同时加密多个数据行,因为每个数据块的加密是独立的。但要确保计数器值对于每个块都是唯一的。
无论选择哪种模式,都需要确保密钥的安全性,并定期更换密钥。
为何CTR模式与数据库结合良好
由于CTR模式的加密操作是独立的,假如你想读取数据库中一个特定的加密块(例如,一个加密的记录或字段),你不需要解密前面的所有块。你只需使用相应的计数器值进行解密即可。
CTR实操
Python
为了给大家一个具体的操作示例,我们首先会使用Python的cryptography
库来完成这个操作。但首先,需要确保你已经安装了这个库:
pip install cryptography
接下来,我们来看看CTR模式下如何使用AES加密"HELLO"这个字符串:
import random
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import ciphers
from cryptography.hazmat.primitives.ciphers import Cipher
# 1. 选择一个密钥
# AES-128位密钥 (16 bytes)
key = b'abcdefghijklmnop'
# 2. 初始化一个计数器 (这只是一个简单的例子, 在实际应用中通常使用更复杂的方法)
nonce = int.to_bytes(random.getrandbits(128), 16, 'big')
# 3. 创建AES Cipher实例并设置为CTR模式
cipher = Cipher(ciphers.algorithms.AES(key), ciphers.modes.CTR(nonce), backend=default_backend())
# 4. 创建一个加密器对象
encryptor = cipher.encryptor()
# 5. 加密"HELLO"字符串
ciphertext = encryptor.update(b'HELLO') + encryptor.finalize()
print("Ciphertext:", ciphertext)
首先,代码从cryptography
库中导入了所需的模块和函数,这个库是一个提供加密和解密功能的Python包。
在代码中,首先选择了一个固定的128位AES密钥,即key = b'abcdefghijklmnop'
。AES支持多种密钥长度,如128位、192位和256位。在这个示例中,选择了128位的密钥,这意味着密钥长度为16字节。
接下来,为了使用CTR模式,我们需要一个名为nonce的计数器。在这个例子中,随机生成了一个128位的数字,并将其转换为一个16字节的字节串作为nonce。请注意,在实际应用中,计数器的使用和管理通常会更加复杂,确保每次加密时使用的nonce都是唯一的。
然后,使用指定的AES密钥和CTR模式的nonce创建了一个Cipher
实例。ciphers.algorithms.AES(key)
定义了使用AES算法,而ciphers.modes.CTR(nonce)
则设定了CTR模式并提供了计数器。这个Cipher
对象将作为加密和解密操作的基础。
随后,使用这个Cipher
实例创建了一个加密器对象,称为encryptor
。这个加密器对象将被用于执行实际的加密操作。
最后,使用这个加密器对字符串"HELLO"进行了加密,并打印出加密后的密文。在CTR模式下,加密操作通常分为两步:先调用update
方法进行加密,然后调用finalize
方法来结束加密过程并获取任何剩余的加密数据。这两步通常一起使用,以确保所有数据都已被正确加密。每一次运行,nonce值不同,所以密文结果是不同的:
这段代码为我们提供了一个简洁的方式,使用AES的CTR模式加密数据。它涵盖了选择密钥、生成计数器、设置加密算法和模式、创建加密器,以及实际进行加密的整个过程。
Python结合数据库
在数据库的上下文中,加密通常发生在以下几个级别:
透明数据加密(TDE): 这是最简单的方式。整个数据库或特定的数据文件被加密。当数据库服务启动时,它会解密数据以进行读取,并在写入时再次加密。用户和应用程序与数据库的交互就好像它未被加密一样,但实际上一直处于加密状态。
列级加密: 只对特定的列进行加密,如信用卡号、身份证号等敏感信息。应用程序在写入或读取这些列时需要执行加密或解密操作。
应用级加密: 在应用程序内部,数据在被写入数据库之前先被加密,然后在被读取后被解密。数据库读数据的时候只看到加密的数据。
现在,假设我们想在列级加密的上下文中使用上面的CTR示例,我们可以这样做:
场景
我们有一个数据库表,它存储用户信息,其中ssn
(社会安全号) 是一个敏感列,需要被加密。
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(100),
ssn BLOB -- We'll store encrypted data here
);
插入数据时的加密
当插入新的用户数据时,我们不直接插入社会安全号。首先,我们使用上面的Python代码对其进行加密,然后再插入加密后的值。
注意是使用Python,这就涉及到后端,并不是在数据库中进行的操作哦!对于数据库本身,它仅存储和检索加密数据,不知道解密方法或密钥。
# ... the AES CTR encryption code ...
user_name = "John Doe"
user_ssn = "123-45-6789" # This is what we want to encrypt
encrypted_ssn = encryptor.update(user_ssn.encode()) + encryptor.finalize()
# Now, use the encrypted_ssn in your SQL INSERT statement
读取数据时的解密
当从数据库中读取用户数据时,ssn
列的值是加密的。因此,需要使用解密器对象来解密它,以便在应用程序中使用。
# ... the AES CTR decryption code ...
# Assume 'encrypted_data_from_db' is the value we fetched from the 'ssn' column for a user
decryptor = cipher.decryptor()
decrypted_ssn = decryptor.update(encrypted_data_from_db) + decryptor.finalize()
print("Decrypted SSN:", decrypted_ssn.decode())
完整的加密解密过程如下:
import random
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import ciphers
from cryptography.hazmat.primitives.ciphers import Cipher
# 1. 选择一个密钥
# AES-128位密钥 (16 bytes)
key = b'abcdefghijklmnop'
# 2. 初始化一个计数器 (这只是一个简单的例子, 在实际应用中通常使用更复杂的方法)
nonce = int.to_bytes(random.getrandbits(128), 16, 'big')
# 3. 创建AES Cipher实例并设置为CTR模式
cipher = Cipher(ciphers.algorithms.AES(key), ciphers.modes.CTR(nonce), backend=default_backend())
# 4. 创建一个加密器对象
encryptor = cipher.encryptor()
# 5. 加密"HELLO"字符串
ciphertext = encryptor.update(b'HELLO') + encryptor.finalize()
print("Ciphertext:", ciphertext)
decryptor = cipher.decryptor()
decrypted_ssn = decryptor.update(ciphertext) + decryptor.finalize()
print("Decrypted_ssn:",decrypted_ssn)
输出:
Springboot
各层代码
作者最终毕设还是Springboot,所以继续学一下,这个挺有趣的!
为了简化,我们将使用固定的加密密钥,但在真实应用中,你应该使用一个安全的方式来生成和存储这个密钥。
首先,需要在你的pom.xml
中添加以下依赖以使用Java加密扩展(JCE):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
这一步不是必须的,添加依赖之后的所有访问之前都需要使用用户名user和Springboot控制台提供的密码登录,这其实是一种安全性检查(在后文中有介绍)。作者一开始添加了依赖,但是在下一篇改进优化部分中死活不返回值,就把这个依赖删了,等以后询问老师再解决吧!
然后,创建服务层,以下是一个简单的加密/解密服务:
import org.springframework.stereotype.Service;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
@Service
public class EncryptionService {
// 请注意:此示例使用固定的密钥和IV。在实际应用中,你应该使用安全的方式生成这些值。
private static final String KEY = "0123456789ABCDEF"; // 16字节
private static final String IV = "1234567890ABCDEF"; // 16字节
private static final String ALGORITHM = "AES";
private static final String MODE = "AES/CTR/NoPadding";
public String encrypt(String plainText) throws Exception {
Cipher cipher = Cipher.getInstance(MODE);
SecretKey secretKey = new SecretKeySpec(KEY.getBytes(StandardCharsets.UTF_8), ALGORITHM);
IvParameterSpec iv = new IvParameterSpec(IV.getBytes(StandardCharsets.UTF_8));
cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
byte[] encryptedBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedBytes);
}
public String decrypt(String encryptedText) throws Exception {
Cipher cipher = Cipher.getInstance(MODE);
SecretKey secretKey = new SecretKeySpec(KEY.getBytes(StandardCharsets.UTF_8), ALGORITHM);
IvParameterSpec iv = new IvParameterSpec(IV.getBytes(StandardCharsets.UTF_8));
cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedText));
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
}
然后定义Controller控制器,用于展示如何使用上述服务:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class EncryptionController {
private final EncryptionService encryptionService;
public EncryptionController(EncryptionService encryptionService) {
this.encryptionService = encryptionService;
}
@GetMapping("/encrypt")
public String encrypt(@RequestParam String text) {
try {
return encryptionService.encrypt(text);
} catch (Exception e) {
return "Error during encryption: " + e.getMessage();
}
}
@GetMapping("/decrypt")
public String decrypt(@RequestParam String encryptedText) {
try {
return encryptionService.decrypt(encryptedText);
} catch (Exception e) {
return "Error during decryption: " + e.getMessage();
}
}
}
这只是一个基本的示例,并且其中的固定密钥和IV并不安全。在生产环境中,你应该使用更为安全的方式来管理你的密钥和IV,例如使用Java的KeyStore或第三方工具。
具体流程
另外,考虑到有些人(作者)稍微有点忘记Springboot操作,现查询基础操作流程:
1. 创建新的Spring Boot项目
最简单的方法是使用Spring Initializr:
- 选择Maven和Java版本。
- 添加依赖:"Spring Web" 和 "Spring Security",但作者这次没加后者。
2. 添加代码
将先前提供的EncryptionService
类和EncryptionController
类添加到项目中。
3. 配置应用
如果你想运行应用在一个特定的端口,你可以在src/main/resources/application.properties
文件中添加以下内容(例如,我们设置为8085端口):
server.port=8085
4. 启动应用
在你的IDE中,查找@SpringBootApplication
注解的主类(它是Spring Initializr为你创建的)。这个类应该有一个public static void main(String[] args)
方法。直接运行这个类。
5. 查看结果
当应用启动后,打开浏览器并访问:
- 加密:http://localhost:8085/encrypt?text=HELLO
- 解密:http://localhost:8085/decrypt?encryptedText=YOUR_ENCRYPTED_STRING_HERE
注意:替换YOUR_ENCRYPTED_STRING_HERE
为实际的加密字符串。
注意,当我们在Spring Boot应用中引入spring-boot-starter-security
依赖时,Spring Boot会默认为你的应用启用安全配置。我们会看到登录界面:
这个默认的安全配置包括一些基础的身份验证和授权设置。
有常见的两种方法,一种是设置Configuration配置类,有点难,作者不是这样解决的。还有一种方案是,需要一个用户名和密码才能访问应用。Spring Boot通常会在控制台上输出一个默认的用户名(user
)和一个随机生成的密码,每次启动应用时密码都会变:
登陆后,服务返回值展示在页面上:
这样就实现了Springboot程序上的CTR!做到这里,是不是很有成就感呢?作者认为在课上所有内容都学,早已不用的方法原理也要学,这可能会打基础,但当前是一定没用的。因为我们需要实操!请大家相信自己,有自己的思考、想法,做最适合自己的事!
当然,这样基础的代码是不够的,作者对于密钥生成和数据完整性检查对于Python和Springboot都做出了改进,并在Springboot中使用post请求,详情见文章《信息安全第三周+》。
密钥分配
密钥分配是许多加密协议和系统中的关键部分。一个系统中的实体(例如用户或设备)需要一个安全的方式来交换或接收密钥,以便进行安全通信。这就引入了两种主要的密钥分配机制:集中式分配和分布式分配。下面是关于这两种机制和密钥分配中心(KDC)的简介:
-
集中式分配方案(Centralized Key Distribution):
- 在这种机制中,有一个中央实体(通常称为密钥分配中心或KDC)负责密钥的生成、存储和分配。
- 当两个实体需要通信时,它们都会联系KDC来获取会话密钥。KDC知道每个实体的长期密钥,并使用这些密钥来安全地传输会话密钥。
- 优势:简单,容易管理,因为所有的密钥管理都集中在一个地方。
- 劣势:单点故障。如果KDC受到攻击或失效,整个系统可能会受到威胁。
Kerberos就是一个广泛使用的基于KDC的身份验证协议。
-
分布式分配方案(Distributed Key Distribution):
- 在分布式系统中,密钥管理是分散的,没有单一的中心实体负责密钥的分配。
- 这通常涉及到复杂的协议,使实体可以直接(或通过少数几个中间实体)安全地交换密钥。
- 优势:没有单点故障,可扩展性好。
- 劣势:可能会比集中式系统更复杂,并需要更多的初始设置和维护。
-
密钥分配中心(KDC):
- KDC是集中式密钥分配方案的核心部分。
- KDC通常包含两个主要组件:一个身份验证服务器(AS)和一个票据授权服务器(TGS)。
- 身份验证服务器(AS):当用户首次登录系统时,他们与AS通信,向其证明自己的身份,并获取一个“票据授权票”(TGT)。
- 票据授权服务器(TGS):当用户需要访问特定服务时,他们使用TGT与TGS通信,请求特定服务的会话密钥。
- KDC使用预共享的秘密密钥或证书与每个用户或服务进行身份验证。
为了使密钥分配更为安全和高效,需要结合多种策略和技术,确保密钥在其生命周期中始终保持安全。