目录
- 一、密码学基础概念
-
- [1.1 加密算法分类](#1.1 加密算法分类)
- [1.2 安全编码场景](#1.2 安全编码场景)
- 二、对称加密实战(AES-128)
-
- [2.1 AES 基础](#2.1 AES 基础)
- [2.2 开源库使用](#2.2 开源库使用)
-
- [2.2.1 加密流程](#2.2.1 加密流程)
- [2.2.2 解密流程](#2.2.2 解密流程)
- [2.2.3 实战](#2.2.3 实战)
- 三、哈希算法实战(SHA-256)
-
- [3.1 SHA-256 基础](#3.1 SHA-256 基础)
- [3.2 开源库使用](#3.2 开源库使用)
-
- [3.2.1 盐值作用](#3.2.1 盐值作用)
- [3.2.2 实战](#3.2.2 实战)
- [3.3 安全编码最佳实践](#3.3 安全编码最佳实践)
- 四、总结与展望
一、密码学基础概念
1.1 加密算法分类
- 对称加密:对称加密算法是指在加密和解密过程中使用同一个密钥的加密方式。这种加密方式的特点是加密和解密速度快,效率高,适合对大量数据进行加密处理。然而,由于加密和解密使用相同的密钥,密钥的安全管理变得至关重要,一旦密钥泄露,加密的数据就不再安全。典型的对称加密算法如 AES(高级加密标准),它是一种分组加密算法,分组大小为 128 位,密钥大小可以是 128 位、192 位或 256 位。AES 有多种工作模式,如 ECB(电子密码本模式)和 CBC(密码块链接模式) ,其中 CBC 模式需要初始化向量 IV,其安全性高于 ECB 模式,因为 ECB 模式对于相同的明文块会加密出相同的密文块,容易被攻击者利用。
- 非对称加密:非对称加密算法使用一对密钥,即公钥和私钥,公钥用于加密数据,私钥用于解密数据。这种加密方式的优势在于解决了密钥分发的问题,因为公钥可以公开传播,而私钥由接收方妥善保管。但是,非对称加密算法的加密和解密速度相对较慢,计算开销较大,通常适用于加密少量数据或用于数字签名、身份认证等场景。RSA 算法是目前应用最广泛的非对称加密算法之一,其安全性基于大数分解的困难性。例如,在网络通信中,服务器可以将自己的公钥发送给客户端,客户端使用公钥对敏感数据进行加密后传输给服务器,只有服务器拥有对应的私钥才能解密数据,从而保证了数据传输的安全性。
- 哈希算法:哈希算法是一种将任意长度的数据转换为固定长度哈希值的算法,也被称为散列算法。哈希算法具有单向性,即从哈希值无法反向推导出原始数据,并且对于不同的输入数据,哈希值具有唯一性(抗碰撞性),即使原始数据只有微小的变化,也会导致生成的哈希值截然不同。哈希算法主要用于数据完整性校验,确保数据在传输或存储过程中未被篡改。例如,SHA-256(安全哈希算法 256 位),它会将任意长度的数据转换为 256 位的哈希值,常用于文件传输后的完整性验证,以及在存储用户密码时,存储密码的哈希值而非明文,增强密码的安全性。
1.2 安全编码场景
- 用户密码存储:在应用程序中,用户密码的安全存储至关重要。如果直接存储用户密码的明文,一旦数据库泄露,用户密码将直接暴露,造成严重的安全风险。因此,通常采用哈希算法对用户密码进行处理,存储密码的哈希值。例如,在用户注册时,系统将用户输入的密码通过 SHA - 256 等哈希算法计算出哈希值,然后将哈希值存储在数据库中。当用户登录时,系统对用户输入的密码再次计算哈希值,并与数据库中存储的哈希值进行比对,如果两者相同,则验证用户密码正确。为了进一步增强安全性,还会为每个密码添加随机盐值(Salt),盐值是一个随机字符串,与密码拼接后再进行哈希计算,这样即使两个用户设置了相同的密码,由于盐值不同,生成的哈希值也会不同,有效防止了彩虹表攻击。
- 敏感数据传输:在网络传输过程中,敏感数据如用户的个人信息、财务数据等需要进行加密,以防止数据被窃取或篡改。对称加密算法由于其高效性,常被用于敏感数据的加密传输。以 AES 算法为例,在数据传输前,发送方和接收方先协商好一个密钥(可以通过安全的方式交换,或者使用非对称加密来交换对称加密的密钥),发送方使用该密钥对敏感数据进行 AES 加密,然后将密文发送给接收方,接收方收到密文后,使用相同的密钥进行解密,从而获取原始的敏感数据。这样,即使数据在传输过程中被截获,攻击者在没有密钥的情况下也无法解密数据,保障了数据传输的安全性。
二、对称加密实战(AES-128)
2.1 AES 基础
AES(Advanced Encryption Standard)作为一种分组加密算法,在现代密码学领域应用广泛。其分组大小固定为 128 位,这意味着在加密过程中,会将明文按 128 位(16 字节)为一组进行处理 。密钥大小则有 128 位、192 位和 256 位三种选择,分别对应 AES-128、AES-192 和 AES-256 算法。密钥长度越长,破解的难度就越大,安全性也就越高,但同时加密和解密的计算开销也会相应增加。在实际应用中,AES-128 因其在安全性和计算效率之间取得了较好的平衡,被大量使用。
AES 拥有多种工作模式,其中 ECB(电子密码本模式)和 CBC(密码块链接模式)较为常见。ECB 模式是将明文分组后,每个分组独立进行加密,相同的明文分组会产生相同的密文分组。这种模式的优点是简单直观,易于实现,并且有利于并行计算。然而,它的缺点也很明显,由于相同明文块加密结果相同,容易被攻击者利用统计分析的方法破解,无法隐藏明文的模式,因此在安全性要求较高的场景中不推荐使用。
CBC 模式则引入了初始化向量 IV(Initialization Vector),在加密第一个明文分组时,先将其与 IV 进行异或操作,然后再进行加密;后续的明文分组则与前一个密文分组进行异或后再加密。这样,即使是相同的明文分组,由于其前面的分组密文不同(或者第一个分组与不同的 IV 异或),加密后的密文也会不同,大大增强了加密的安全性,适合传输长度较长的报文,是 SSL、IPSec 等协议的标准加密模式 。不过,CBC 模式由于需要依赖前一个分组的处理结果,不利于并行计算,并且如果在传输过程中某个密文分组出现错误,会影响到后续分组的解密,存在误差传递的问题。
2.2 开源库使用
在 C 语言开发中,为了实现 AES-128 加密和解密功能,可以集成 tiny-AES-c 库。这是一个轻量级、跨平台的 AES 加密库,非常适合在资源受限的环境中使用,其代码量小,并且不依赖于其他复杂的第三方库,易于集成到项目中。
2.2.1 加密流程
- 初始化密钥与 IV:首先,需要定义并初始化 AES 加密所需的密钥和 IV。密钥的长度为 128 位(16 字节),IV 的长度也为 16 字节,且 IV 必须是随机生成的,以确保每次加密的安全性。例如:
c
#include "aes.h"
// 定义128位密钥
uint8_t key[AES_BLOCKLEN] = {0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c};
// 定义初始化向量IV
uint8_t iv[AES_BLOCKLEN] = {0};
- 对数据进行 PKCS#7 填充:由于 AES 是分组加密算法,要求明文数据的长度必须是 16 字节的倍数。如果数据长度不足,就需要进行填充。PKCS#7 填充是一种常用的填充方式,它会在数据末尾填充若干个字节,每个字节的值等于需要填充的字节数。例如,如果数据长度为 13 字节,那么需要填充 3 个字节,每个字节的值都是 0x03。下面是一个简单的 PKCS#7 填充函数示例:
c
// PKCS#7填充
void pkcs7_padding(uint8_t *data, size_t data_len, size_t block_size) {
size_t padding = block_size - (data_len % block_size);
for (size_t i = 0; i < padding; i++) {
data[data_len + i] = (uint8_t)padding;
}
}
- 调用 AES-CBC 加密函数:完成密钥、IV 初始化和数据填充后,即可调用 tiny-AES-c 库中的 AES-CBC 加密函数对数据进行加密。在调用函数时,需要传入待加密的数据、加密后的密文存储缓冲区、数据长度、密钥以及 IV 等参数。示例代码如下:
c
aes_context ctx;
// 设置密钥并初始化上下文
aes_set_key(&ctx, key, sizeof(key));
// 执行加密操作
aes_cbc_encrypt(data, encrypted_data, data_len, &ctx, iv);
2.2.2 解密流程
- 使用相同密钥与 IV:解密过程需要使用与加密时相同的密钥和 IV,以确保能够正确还原出原始明文。在实际应用中,密钥和 IV 的安全存储和传递至关重要,一旦泄露,加密的数据就会失去安全性。
c
// 使用与加密相同的密钥和IV
uint8_t key[AES_BLOCKLEN] = {0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c};
uint8_t iv[AES_BLOCKLEN] = {0};
- 调用 AES-CBC 解密函数:调用 tiny-AES-c 库中的 AES-CBC 解密函数,传入密文、解密后的明文存储缓冲区、密文长度、密钥以及 IV 等参数,即可进行解密操作。示例代码如下:
c
aes_context ctx;
// 设置密钥并初始化上下文
aes_set_key(&ctx, key, sizeof(key));
// 执行解密操作
aes_cbc_decrypt(encrypted_data, decrypted_data, data_len, &ctx, iv);
- 去除填充:解密后的明文可能包含填充数据,需要根据填充方式去除这些填充字节,以得到原始的明文数据。对于 PKCS#7 填充方式,去除填充的方法是读取最后一个字节的值,该值表示填充的字节数,然后将这些填充字节从明文末尾移除。示例代码如下:
c
// 去除PKCS#7填充
void pkcs7_unpadding(uint8_t *data, size_t data_len) {
size_t padding = (size_t)data[data_len - 1];
if (padding > data_len) {
return;
}
data_len -= padding;
data[data_len] = '\0';
}
2.2.3 实战
以 "密码管理器" 为例,假设我们需要对其中存储的密码数据进行加密存储,以提高密码的安全性。在用户输入密码后,应用程序首先获取密码数据,然后按照上述 AES-128 加密流程对密码进行加密,将加密后的密文存储到数据库或其他存储介质中。示例代码如下:
c
#include <stdio.h>
#include <string.h>
#include "aes.h"
// PKCS#7填充
void pkcs7_padding(uint8_t *data, size_t data_len, size_t block_size) {
size_t padding = block_size - (data_len % block_size);
for (size_t i = 0; i < padding; i++) {
data[data_len + i] = (uint8_t)padding;
}
}
// 去除PKCS#7填充
void pkcs7_unpadding(uint8_t *data, size_t data_len) {
size_t padding = (size_t)data[data_len - 1];
if (padding > data_len) {
return;
}
data_len -= padding;
data[data_len] = '\0';
}
int main() {
// 定义128位密钥
uint8_t key[AES_BLOCKLEN] = {0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c};
// 定义初始化向量IV
uint8_t iv[AES_BLOCKLEN] = {0};
// 假设用户输入的密码
char password[] = "user_password";
size_t password_len = strlen(password);
// 加密后的密文存储缓冲区
uint8_t encrypted_password[1024];
size_t encrypted_password_len;
// 解密后的明文存储缓冲区
uint8_t decrypted_password[1024];
size_t decrypted_password_len;
// 对密码进行PKCS#7填充
pkcs7_padding((uint8_t *)password, password_len, AES_BLOCKLEN);
password_len += AES_BLOCKLEN - (password_len % AES_BLOCKLEN);
aes_context ctx;
// 设置密钥并初始化上下文
aes_set_key(&ctx, key, sizeof(key));
// 执行加密操作
aes_cbc_encrypt((uint8_t *)password, encrypted_password, password_len, &ctx, iv);
encrypted_password_len = password_len;
// 模拟存储加密后的密文,这里只是简单打印密文
printf("加密后的密文: ");
for (size_t i = 0; i < encrypted_password_len; i++) {
printf("%02x ", encrypted_password[i]);
}
printf("\n");
// 执行解密操作
aes_cbc_decrypt(encrypted_password, decrypted_password, encrypted_password_len, &ctx, iv);
decrypted_password_len = encrypted_password_len;
// 去除填充
pkcs7_unpadding(decrypted_password, decrypted_password_len);
// 打印解密后的明文
printf("解密后的明文: %s\n", decrypted_password);
return 0;
}
当用户需要查询密码时,应用程序从存储介质中读取加密的密文,然后按照解密流程进行解密,将解密后的明文展示给用户(实际应用中可能不会直接展示明文,而是用于其他验证操作)。通过这种方式,有效保护了密码数据的安全,即使存储介质中的密文被泄露,没有正确的密钥和 IV,攻击者也无法获取用户的真实密码。
三、哈希算法实战(SHA-256)
3.1 SHA-256 基础
SHA-256(Secure Hash Algorithm 256-bit)是 SHA-2 系列哈希算法中的一种,在信息安全领域扮演着关键角色 。它能够将任意长度的数据通过复杂的数学运算,转换为固定长度的 256 位哈希值,这个哈希值通常以 32 字节的十六进制字符串形式呈现。
从原理上来说,SHA-256 首先会对输入数据进行填充,使其长度满足特定条件,即附加一个 "1" 位,再添加若干个 "0" 位,使得填充后的消息长度比 512 的倍数少 64 位,最后将用 64 位二进制表示的消息长度附加到消息末尾 。接着,算法会初始化八个 32 字节(256 位)的初始哈希值,然后将消息分割成 512 位(64 字节)的消息块,每个消息块进一步分为 16 个 32 位的字。在消息调度阶段,将 16 个 32 位的字扩展为 64 个 32 位的字,随后进入主循环,进行 64 轮压缩操作,每轮使用一个常量 K [i] 和一个消息字 w [i]。完成所有消息块的处理后,将计算结果加到当前哈希值,最终将 h0 到 h7 连接起来,形成 256 位(32 字节)的最终哈希值 。
在数据完整性校验方面,SHA-256 发挥着不可替代的作用。例如,在文件传输过程中,发送方可以计算文件的 SHA-256 哈希值,并将其与文件一同发送给接收方。接收方在收到文件后,重新计算文件的 SHA-256 哈希值,然后将计算得到的哈希值与发送方提供的哈希值进行比对。如果两个哈希值相同,就可以确认文件在传输过程中没有被篡改;反之,如果哈希值不同,那么文件很可能在传输过程中遭到了修改或破坏。
在用户密码存储场景中,SHA-256 同样是保障密码安全的重要手段。传统的做法是直接存储用户密码的明文,这种方式存在极大的安全风险,一旦数据库泄露,用户密码将全部暴露。而使用 SHA-256 对用户密码进行哈希处理后,存储的是密码的哈希值而非明文。当用户登录时,系统对用户输入的密码计算 SHA-256 哈希值,并与数据库中存储的哈希值进行比对,从而验证用户密码的正确性 。不过,为了进一步增强密码的安全性,通常还会引入盐值(Salt)的概念,后面会详细介绍。
3.2 开源库使用
在 C 语言中,为了实现 SHA-256 哈希值的计算,可以集成 cSHAKE 库。cSHAKE 库是一个功能强大且易于使用的开源库,它提供了丰富的接口和函数,方便开发者在项目中快速实现 SHA-256 相关的功能。
3.2.1 盐值作用
盐值(Salt)是一段随机生成的数据,通常为 16 字节或更长 ,在密码存储过程中起着至关重要的作用,主要用于增强密码的安全性,防止彩虹表攻击。
彩虹表是一种预先计算好的包含大量 "明文 - 哈希值" 映射关系的数据库。如果没有盐值,当多个用户设置相同的密码时,这些相同密码经过哈希算法计算后会得到相同的哈希值。攻击者一旦获取了数据库中的哈希值,就可以通过查询彩虹表,快速找到对应的明文密码,从而破解用户账户 。
而引入盐值后,每个用户的密码在进行哈希计算前,都会与一个唯一的盐值进行拼接,然后再计算哈希值。这样,即使两个用户设置了相同的密码,由于盐值不同,最终生成的哈希值也会截然不同。例如,用户 A 的密码是 "password",生成的盐值是 "salt1",拼接后的字符串为 "passwordsalt1",计算其 SHA-256 哈希值为 "hash1";用户 B 的密码同样是 "password",但盐值是 "salt2",拼接后的字符串为 "passwordsalt2",计算得到的哈希值为 "hash2","hash1" 和 "hash2" 是完全不同的 。这就使得攻击者无法通过预先计算好的彩虹表来破解密码,大大增加了密码破解的难度和成本,有效提升了密码存储的安全性。
3.2.2 实战
以用户注册和登录功能为例,详细介绍如何利用 cSHAKE 库结合盐值来实现安全的密码验证机制。
在用户注册时:
c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include "cSHAKE.h"
// 生成随机盐值,这里简单示例为16字节
void generate_salt(char *salt, size_t salt_len) {
const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
for (size_t i = 0; i < salt_len; i++) {
salt[i] = charset[rand() % (sizeof(charset) - 1)];
}
salt[salt_len] = '\0';
}
// 计算"密码 + 盐值"的SHA-256哈希值
void compute_sha256(const char *password, const char *salt, char *hash) {
char combined[1024];
snprintf(combined, sizeof(combined), "%s%s", password, salt);
cSHAKE(combined, strlen(combined), hash, 32);
}
int main() {
srand(time(NULL));
char password[100];
printf("请输入注册密码: ");
scanf("%s", password);
char salt[17];
generate_salt(salt, 16);
char hash[65]; // 32字节的哈希值转换为十六进制字符串需要64位,加上字符串结束符'\0'
compute_sha256(password, salt, hash);
// 模拟存储哈希值和盐值,这里只是简单打印
printf("存储的哈希值: %s\n", hash);
printf("存储的盐值: %s\n", salt);
return 0;
}
上述代码中,generate_salt函数用于生成 16 字节的随机盐值,compute_sha256函数则将用户输入的密码和生成的盐值拼接后,利用 cSHAKE 库计算其 SHA-256 哈希值 。在实际应用中,会将计算得到的哈希值和盐值存储到数据库中。
在用户登录时:
c
#include <stdio.h>
#include <string.h>
#include "cSHAKE.h"
// 计算"密码 + 盐值"的SHA-256哈希值
void compute_sha256(const char *password, const char *salt, char *hash) {
char combined[1024];
snprintf(combined, sizeof(combined), "%s%s", password, salt);
cSHAKE(combined, strlen(combined), hash, 32);
}
int main() {
char password[100];
printf("请输入登录密码: ");
scanf("%s", password);
// 假设从数据库中获取到的盐值和哈希值
char stored_salt[17] = "your_stored_salt";
char stored_hash[65] = "your_stored_hash";
char input_hash[65];
compute_sha256(password, stored_salt, input_hash);
if (strcmp(input_hash, stored_hash) == 0) {
printf("登录成功\n");
} else {
printf("密码错误\n");
}
return 0;
}
在登录过程中,首先获取用户输入的密码,然后使用从数据库中读取的盐值与输入密码拼接,再次利用 cSHAKE 库计算其 SHA-256 哈希值 。最后将计算得到的哈希值与数据库中存储的哈希值进行比对,如果两者相同,则表示用户输入的密码正确,登录成功;否则,提示密码错误。通过这种方式,有效保障了用户密码的安全性,即使数据库中的哈希值和盐值泄露,攻击者在没有获取到原始密码的情况下,也难以通过哈希值破解用户密码。
3.3 安全编码最佳实践
在使用哈希算法和进行密码学相关的安全编码时,遵循一些最佳实践原则至关重要,可以有效提升系统的安全性,降低安全风险。
首先,应坚决避免使用过时的算法,如 DES(Data Encryption Standard)和 MD5(Message - Digest Algorithm 5) 。DES 算法由于其密钥长度较短(56 位),在现代计算能力下,已经能够被相对容易地破解,无法满足当前对数据安全性的高要求。MD5 算法虽然曾经被广泛应用,但后来被发现存在严重的安全漏洞,如容易产生碰撞,即不同的输入数据可能会生成相同的哈希值,这使得它在需要高度安全性的场景中不再适用,例如在用户密码存储和数据完整性校验等关键领域,使用 MD5 算法可能会导致严重的安全问题,因此应选择更安全、更强大的算法,如 AES 用于对称加密,SHA-256 及其更高级的变体用于哈希计算。
其次,密钥的存储位置和方式关乎整个加密体系的安全性。在嵌入式设备等应用场景中,密钥不应硬编码在代码中,因为这样一旦代码泄露,密钥也会随之暴露,导致加密数据完全失去保护 。理想的做法是将密钥存储在安全的位置,如嵌入式设备的加密 Flash 中。加密 Flash 提供了一定的硬件级别的加密保护,能够防止密钥被轻易读取或篡改 。同时,对于密钥的管理,应采用严格的访问控制策略,只有经过授权的程序模块才能访问密钥,并且在密钥的使用过程中,要尽量减少其在内存中暴露的时间,使用完毕后及时清理内存,避免密钥被窃取。
此外,对于密码的处理,除了使用加盐的哈希算法外,还可以考虑使用更高级的密码哈希函数,如 bcrypt、scrypt 或 Argon2 等 。这些函数不仅内置了盐值管理功能,还能够通过动态调整计算成本(如迭代次数、内存占用等),来抵御硬件加速破解,进一步增强密码的安全性。同时,在系统设计中,应定期更新密码存储方案和加密算法,以适应不断变化的安全威胁,例如当出现新的密码破解技术或加密算法漏洞时,能够及时采取措施进行升级和修复,确保系统的安全性始终处于较高水平。
四、总结与展望
在 C 语言的安全编码领域,密码学技术为数据的保密性、完整性和认证性提供了关键支持。通过深入理解对称加密、非对称加密以及哈希算法的基础概念,我们掌握了不同加密方式的适用场景和原理。在对称加密实战中,AES-128 算法凭借其高效性和安全性,成为保护敏感数据的有力工具,借助 tiny-AES-c 库,我们能够轻松实现数据的加密和解密,确保数据在存储和传输过程中的安全。哈希算法方面,SHA-256 以其不可逆性和强大的数据完整性校验能力,在用户密码存储和文件完整性验证等场景中发挥着不可或缺的作用,结合盐值的使用,进一步增强了密码存储的安全性,有效抵御了常见的密码破解攻击。
展望未来,随着信息技术的飞速发展,安全编码领域将面临更多的挑战和机遇。量子计算技术的崛起可能对传统的密码算法构成威胁,后量子密码学有望成为研究热点,C 语言开发者需要关注这一领域的进展,探索如何在 C 语言项目中应用抗量子密码算法,以确保数据的长期安全性。在物联网、人工智能等新兴领域,C 语言作为一种高效、灵活的编程语言,将继续发挥重要作用,我们需要根据这些领域的特点和需求,进一步优化密码学算法和安全编码实践,例如在资源受限的物联网设备中,开发更加轻量级、高效的加密算法和密钥管理方案。随着安全威胁的不断演变,安全编码的理念和技术也将持续更新,开发者需要不断学习和掌握新的安全编码技术和最佳实践,加强对代码安全性的审核和测试,以构建更加安全可靠的软件系统,为信息安全保驾护航。