Files
sharp-basic/test/Common.Test/CryptoHelperTests.cs
T
2025-12-30 09:17:25 +08:00

739 lines
21 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Security.Cryptography;
using Common.Utils;
namespace Common.Test;
/// <summary>
/// CryptoHelper 加密工具测试
/// 展示各种加密、哈希、编码功能
/// </summary>
[TestClass]
public class CryptoHelperTests
{
#region AES 加密解密测试
/// <summary>
/// 测试:AES 加密和解密
/// </summary>
[TestMethod]
public void AesEncrypt_AndDecrypt_ReturnsOriginalText()
{
// Arrange
var plainText = "Hello, World! 你好世界!";
var password = "MySecretPassword123";
// Act
var encrypted = CryptoHelper.AesEncrypt(plainText, password);
var decrypted = CryptoHelper.AesDecrypt(encrypted, password);
// Assert
Assert.AreNotEqual(plainText, encrypted);
Assert.AreEqual(plainText, decrypted);
}
/// <summary>
/// 测试:相同内容使用相同密码多次加密,结果应该不同(因为 Salt 随机)
/// </summary>
[TestMethod]
public void AesEncrypt_SameTextSamePassword_ProducesDifferentCiphertext()
{
// Arrange
var plainText = "Test Message";
var password = "password";
// Act
var encrypted1 = CryptoHelper.AesEncrypt(plainText, password);
var encrypted2 = CryptoHelper.AesEncrypt(plainText, password);
// Assert: 密文应该不同(因为使用了随机 Salt)
Assert.AreNotEqual(encrypted1, encrypted2);
// 但都应该能正确解密
Assert.AreEqual(plainText, CryptoHelper.AesDecrypt(encrypted1, password));
Assert.AreEqual(plainText, CryptoHelper.AesDecrypt(encrypted2, password));
}
/// <summary>
/// 测试:使用错误密码解密失败
/// </summary>
[TestMethod]
public void AesDecrypt_WrongPassword_ThrowsException()
{
// Arrange
var plainText = "Secret Message";
var correctPassword = "correct";
var wrongPassword = "wrong";
var encrypted = CryptoHelper.AesEncrypt(plainText, correctPassword);
// Act & Assert
Assert.ThrowsException<CryptographicException>(() => { CryptoHelper.AesDecrypt(encrypted, wrongPassword); });
}
/// <summary>
/// 测试:加密空字符串抛出异常
/// </summary>
[TestMethod]
public void AesEncrypt_EmptyString_ThrowsException()
{
Assert.ThrowsException<ArgumentNullException>(() => { CryptoHelper.AesEncrypt("", "password"); });
}
#endregion
#region RSA 加密解密测试
/// <summary>
/// 测试:生成 RSA 密钥对
/// </summary>
[TestMethod]
public void GenerateRsaKeys_ReturnsPublicAndPrivateKeys()
{
// Act
var (publicKey, privateKey) = CryptoHelper.GenerateRsaKeys();
// Assert
Assert.IsFalse(string.IsNullOrEmpty(publicKey));
Assert.IsFalse(string.IsNullOrEmpty(privateKey));
Assert.AreNotEqual(publicKey, privateKey);
}
/// <summary>
/// 测试:RSA 公钥加密,私钥解密
/// </summary>
[TestMethod]
public void RsaEncrypt_AndDecrypt_ReturnsOriginalText()
{
// Arrange
var plainText = "RSA Test Message";
var (publicKey, privateKey) = CryptoHelper.GenerateRsaKeys();
// Act
var encrypted = CryptoHelper.RsaEncrypt(plainText, publicKey);
var decrypted = CryptoHelper.RsaDecrypt(encrypted, privateKey);
// Assert
Assert.AreNotEqual(plainText, encrypted);
Assert.AreEqual(plainText, decrypted);
}
/// <summary>
/// 测试:RSA 加密相同内容产生不同密文(OAEP 填充)
/// </summary>
[TestMethod]
public void RsaEncrypt_SameText_ProducesDifferentCiphertext()
{
// Arrange
var plainText = "Test";
var (publicKey, _) = CryptoHelper.GenerateRsaKeys();
// Act
var encrypted1 = CryptoHelper.RsaEncrypt(plainText, publicKey);
var encrypted2 = CryptoHelper.RsaEncrypt(plainText, publicKey);
// Assert: 由于 OAEP 填充使用随机数,密文应该不同
Assert.AreNotEqual(encrypted1, encrypted2);
}
/// <summary>
/// 测试:使用错误的私钥解密失败
/// </summary>
[TestMethod]
public void RsaDecrypt_WrongPrivateKey_ThrowsException()
{
// Arrange
var plainText = "Secret";
var (publicKey1, _) = CryptoHelper.GenerateRsaKeys();
var (_, privateKey2) = CryptoHelper.GenerateRsaKeys(); // 不同的密钥对
var encrypted = CryptoHelper.RsaEncrypt(plainText, publicKey1);
// Act & Assert
Assert.ThrowsException<CryptographicException>(() => { CryptoHelper.RsaDecrypt(encrypted, privateKey2); });
}
#endregion
#region 哈希函数测试
/// <summary>
/// 测试:MD5 哈希
/// </summary>
[TestMethod]
public void Md5Hash_ProducesCorrectHash()
{
// Arrange
var input = "Hello, World!";
// Act
var hash = CryptoHelper.Md5Hash(input);
// Assert
Assert.AreEqual(32, hash.Length); // MD5 产生 32 位十六进制字符串
Assert.AreEqual("65a8e27d8879283831b664bd8b7f0ad4", hash);
}
/// <summary>
/// 测试:SHA256 哈希
/// </summary>
[TestMethod]
public void Sha256Hash_ProducesCorrectHash()
{
// Arrange
var input = "Hello, World!";
// Act
var hash = CryptoHelper.Sha256Hash(input);
// Assert
Assert.AreEqual(64, hash.Length); // SHA256 产生 64 位十六进制字符串
Assert.AreEqual("dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f", hash);
}
/// <summary>
/// 测试:SHA512 哈希
/// </summary>
[TestMethod]
public void Sha512Hash_ProducesCorrectHash()
{
// Arrange
var input = "Hello, World!";
// Act
var hash = CryptoHelper.Sha512Hash(input);
// Assert
Assert.AreEqual(128, hash.Length); // SHA512 产生 128 位十六进制字符串
}
/// <summary>
/// 测试:相同输入产生相同哈希
/// </summary>
[TestMethod]
public void Hash_SameInput_ProducesSameHash()
{
// Arrange
var input = "Test Input";
// Act
var md5_1 = CryptoHelper.Md5Hash(input);
var md5_2 = CryptoHelper.Md5Hash(input);
var sha256_1 = CryptoHelper.Sha256Hash(input);
var sha256_2 = CryptoHelper.Sha256Hash(input);
// Assert
Assert.AreEqual(md5_1, md5_2);
Assert.AreEqual(sha256_1, sha256_2);
}
/// <summary>
/// 测试:不同输入产生不同哈希
/// </summary>
[TestMethod]
public void Hash_DifferentInput_ProducesDifferentHash()
{
// Arrange
var input1 = "Input1";
var input2 = "Input2";
// Act
var hash1 = CryptoHelper.Sha256Hash(input1);
var hash2 = CryptoHelper.Sha256Hash(input2);
// Assert
Assert.AreNotEqual(hash1, hash2);
}
#endregion
#region Base64 编解码测试
/// <summary>
/// 测试:Base64 编码和解码
/// </summary>
[TestMethod]
public void Base64Encode_AndDecode_ReturnsOriginalText()
{
// Arrange
var input = "Hello, World! 你好世界!";
// Act
var encoded = CryptoHelper.Base64Encode(input);
var decoded = CryptoHelper.Base64Decode(encoded);
// Assert
Assert.AreNotEqual(input, encoded);
Assert.AreEqual(input, decoded);
Assert.AreEqual("SGVsbG8sIFdvcmxkISDkvaDlpb3kuJbnlYzvvIE=", encoded);
}
/// <summary>
/// 测试:URL 安全的 Base64 编码
/// </summary>
[TestMethod]
public void Base64UrlEncode_ProducesUrlSafeString()
{
// Arrange
var input = "Hello+World/Test=";
// Act
var encoded = CryptoHelper.Base64UrlEncode(input);
var decoded = CryptoHelper.Base64UrlDecode(encoded);
// Assert
Assert.IsFalse(encoded.Contains('+')); // 不包含 +
Assert.IsFalse(encoded.Contains('/')); // 不包含 /
Assert.IsFalse(encoded.Contains('=')); // 不包含 =
Assert.AreEqual(input, decoded);
}
/// <summary>
/// 测试:URL Base64 编解码
/// </summary>
[TestMethod]
public void Base64UrlEncode_AndDecode_ReturnsOriginalText()
{
// Arrange
var testCases = new[]
{
"test",
"test1",
"test12",
"test123",
"Hello, World!",
"特殊字符 !@#$%^&*()"
};
foreach (var input in testCases)
{
// Act
var encoded = CryptoHelper.Base64UrlEncode(input);
var decoded = CryptoHelper.Base64UrlDecode(encoded);
// Assert
Assert.AreEqual(input, decoded, $"Failed for input: {input}");
}
}
#endregion
#region 密码哈希测试
/// <summary>
/// 测试:哈希密码并验证
/// </summary>
[TestMethod]
public void HashPassword_AndVerify_ReturnsTrue()
{
// Arrange
var password = "MySecurePassword123!";
// Act
var hashed = CryptoHelper.HashPassword(password);
var isValid = CryptoHelper.VerifyPassword(password, hashed);
// Assert
Assert.IsTrue(isValid);
Assert.AreNotEqual(password, hashed);
Assert.IsTrue(hashed.StartsWith("$pbkdf2-sha256$"));
}
/// <summary>
/// 测试:验证错误密码返回 false
/// </summary>
[TestMethod]
public void VerifyPassword_WrongPassword_ReturnsFalse()
{
// Arrange
var correctPassword = "correct";
var wrongPassword = "wrong";
var hashed = CryptoHelper.HashPassword(correctPassword);
// Act
var isValid = CryptoHelper.VerifyPassword(wrongPassword, hashed);
// Assert
Assert.IsFalse(isValid);
}
/// <summary>
/// 测试:相同密码多次哈希产生不同结果
/// </summary>
[TestMethod]
public void HashPassword_SamePassword_ProducesDifferentHashes()
{
// Arrange
var password = "password123";
// Act
var hash1 = CryptoHelper.HashPassword(password);
var hash2 = CryptoHelper.HashPassword(password);
// Assert
Assert.AreNotEqual(hash1, hash2); // 不同的 Salt
// 但都应该能验证通过
Assert.IsTrue(CryptoHelper.VerifyPassword(password, hash1));
Assert.IsTrue(CryptoHelper.VerifyPassword(password, hash2));
}
/// <summary>
/// 测试:自定义哈希参数
/// </summary>
[TestMethod]
public void HashPassword_CustomParameters_Works()
{
// Arrange
var password = "test";
// Act
var hash = CryptoHelper.HashPassword(password, 32, 50000, 64);
var isValid = CryptoHelper.VerifyPassword(password, hash);
// Assert
Assert.IsTrue(isValid);
}
#endregion
#region HMAC 签名测试
/// <summary>
/// 测试:HMAC-SHA256 签名和验证
/// </summary>
[TestMethod]
public void HmacSha256_AndVerify_ReturnsTrue()
{
// Arrange
var message = "Important message";
var secret = "secret-key";
// Act
var signature = CryptoHelper.HmacSha256(message, secret);
var isValid = CryptoHelper.VerifyHmacSha256(message, signature, secret);
// Assert
Assert.IsTrue(isValid);
Assert.AreEqual(64, signature.Length); // SHA256 产生 64 位十六进制
}
/// <summary>
/// 测试:验证错误签名返回 false
/// </summary>
[TestMethod]
public void VerifyHmacSha256_WrongSignature_ReturnsFalse()
{
// Arrange
var message = "message";
var secret = "secret";
var wrongSignature = "wrong_signature";
// Act
var isValid = CryptoHelper.VerifyHmacSha256(message, wrongSignature, secret);
// Assert
Assert.IsFalse(isValid);
}
/// <summary>
/// 测试:修改消息后签名无效
/// </summary>
[TestMethod]
public void VerifyHmacSha256_ModifiedMessage_ReturnsFalse()
{
// Arrange
var originalMessage = "Original message";
var modifiedMessage = "Modified message";
var secret = "secret";
var signature = CryptoHelper.HmacSha256(originalMessage, secret);
// Act
var isValid = CryptoHelper.VerifyHmacSha256(modifiedMessage, signature, secret);
// Assert
Assert.IsFalse(isValid);
}
/// <summary>
/// 测试:HMAC-SHA512 签名
/// </summary>
[TestMethod]
public void HmacSha512_ProducesCorrectSignature()
{
// Arrange
var message = "Test message";
var secret = "secret";
// Act
var signature = CryptoHelper.HmacSha512(message, secret);
var isValid = CryptoHelper.VerifyHmacSha512(message, signature, secret);
// Assert
Assert.IsTrue(isValid);
Assert.AreEqual(128, signature.Length); // SHA512 产生 128 位十六进制
}
/// <summary>
/// 测试:相同消息和密钥产生相同签名
/// </summary>
[TestMethod]
public void HmacSha256_SameInputs_ProducesSameSignature()
{
// Arrange
var message = "message";
var secret = "secret";
// Act
var signature1 = CryptoHelper.HmacSha256(message, secret);
var signature2 = CryptoHelper.HmacSha256(message, secret);
// Assert
Assert.AreEqual(signature1, signature2);
}
#endregion
#region 辅助方法测试
/// <summary>
/// 测试:生成随机字符串
/// </summary>
[TestMethod]
public void GenerateRandomString_ProducesCorrectLength()
{
// Arrange & Act
var str10 = CryptoHelper.GenerateRandomString(10);
var str20 = CryptoHelper.GenerateRandomString(20);
var strWithSpecial = CryptoHelper.GenerateRandomString(15, true);
// Assert
Assert.AreEqual(10, str10.Length);
Assert.AreEqual(20, str20.Length);
Assert.AreEqual(15, strWithSpecial.Length);
}
/// <summary>
/// 测试:生成随机密码
/// </summary>
[TestMethod]
public void GeneratePassword_MeetsRequirements()
{
// Act
var password = CryptoHelper.GeneratePassword(16);
// Assert
Assert.AreEqual(16, password.Length);
Assert.IsTrue(password.Any(char.IsDigit), "Should contain digit");
Assert.IsTrue(password.Any(char.IsLower), "Should contain lowercase");
Assert.IsTrue(password.Any(char.IsUpper), "Should contain uppercase");
Assert.IsTrue(password.Any(c => !char.IsLetterOrDigit(c)), "Should contain special char");
}
/// <summary>
/// 测试:生成仅包含字母和数字的密码
/// </summary>
[TestMethod]
public void GeneratePassword_OnlyAlphanumeric_NoSpecialChars()
{
// Act
var password = CryptoHelper.GeneratePassword(
12,
true,
true,
true,
false
);
// Assert
Assert.AreEqual(12, password.Length);
Assert.IsTrue(password.All(c => char.IsLetterOrDigit(c)), "Should only contain alphanumeric");
}
/// <summary>
/// 测试:生成多个随机字符串应该不同
/// </summary>
[TestMethod]
public void GenerateRandomString_MultipleCalls_ProducesDifferentResults()
{
// Act
var strings = Enumerable.Range(0, 10)
.Select(_ => CryptoHelper.GenerateRandomString(20))
.ToList();
// Assert: 所有字符串应该不同
Assert.AreEqual(10, strings.Distinct().Count());
}
#endregion
#region 实际应用场景测试
/// <summary>
/// 场景测试:用户密码存储和验证
/// </summary>
[TestMethod]
public void Scenario_UserPasswordStorageAndVerification()
{
// 模拟用户注册
var userPassword = "SecurePassword123!";
var hashedPassword = CryptoHelper.HashPassword(userPassword);
// 存储 hashedPassword 到数据库...
// 模拟用户登录
var loginPassword = "SecurePassword123!";
var isAuthenticated = CryptoHelper.VerifyPassword(loginPassword, hashedPassword);
Assert.IsTrue(isAuthenticated);
// 错误密码
var wrongPassword = "WrongPassword";
var isFailed = CryptoHelper.VerifyPassword(wrongPassword, hashedPassword);
Assert.IsFalse(isFailed);
}
/// <summary>
/// 场景测试:API 签名验证
/// </summary>
[TestMethod]
public void Scenario_ApiSignatureVerification()
{
// 客户端准备 API 请求
var apiKey = "client-api-key";
var apiSecret = "client-api-secret";
var requestBody = "{\"userId\":123,\"action\":\"purchase\"}";
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
// 生成签名
var signatureData = $"{apiKey}{requestBody}{timestamp}";
var signature = CryptoHelper.HmacSha256(signatureData, apiSecret);
// 发送请求(包含 signature 和 timestamp)...
// 服务器端验证签名
var receivedSignatureData = $"{apiKey}{requestBody}{timestamp}";
var isValidSignature = CryptoHelper.VerifyHmacSha256(
receivedSignatureData,
signature,
apiSecret
);
Assert.IsTrue(isValidSignature);
}
/// <summary>
/// 场景测试:敏感数据加密存储
/// </summary>
[TestMethod]
public void Scenario_EncryptSensitiveData()
{
// 加密敏感数据(如信用卡号)
var creditCardNumber = "4532-1234-5678-9010";
var encryptionKey = "DataEncryptionKey123!";
var encrypted = CryptoHelper.AesEncrypt(creditCardNumber, encryptionKey);
// 存储加密后的数据...
Console.WriteLine($"Encrypted: {encrypted}");
// 需要使用时解密
var decrypted = CryptoHelper.AesDecrypt(encrypted, encryptionKey);
Assert.AreEqual(creditCardNumber, decrypted);
}
/// <summary>
/// 场景测试:文件完整性验证
/// </summary>
[TestMethod]
public void Scenario_FileIntegrityVerification()
{
// 模拟文件内容
var fileContent = "Important file content that should not be modified";
// 计算文件哈希
var originalHash = CryptoHelper.Sha256Hash(fileContent);
// 存储哈希值...
// 后续验证文件是否被篡改
var currentHash = CryptoHelper.Sha256Hash(fileContent);
Assert.AreEqual(originalHash, currentHash, "File has not been modified");
// 模拟文件被修改
var modifiedContent = fileContent + " (modified)";
var modifiedHash = CryptoHelper.Sha256Hash(modifiedContent);
Assert.AreNotEqual(originalHash, modifiedHash, "File has been modified");
}
/// <summary>
/// 场景测试:JWT Token 签名(简化版)
/// </summary>
[TestMethod]
public void Scenario_JwtTokenSignature()
{
// JWT Header 和 Payload
var header = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
var payload = "{\"userId\":123,\"exp\":1234567890}";
var headerEncoded = CryptoHelper.Base64UrlEncode(header);
var payloadEncoded = CryptoHelper.Base64UrlEncode(payload);
var tokenWithoutSignature = $"{headerEncoded}.{payloadEncoded}";
// 使用 HMAC 签名
var secret = "jwt-secret-key";
var signature = CryptoHelper.HmacSha256(tokenWithoutSignature, secret);
var signatureEncoded = CryptoHelper.Base64UrlEncode(signature);
var fullToken = $"{tokenWithoutSignature}.{signatureEncoded}";
Console.WriteLine($"JWT Token: {fullToken}");
// 验证 Token
var parts = fullToken.Split('.');
var receivedSignature = CryptoHelper.Base64UrlDecode(parts[2]);
var expectedSignature = CryptoHelper.HmacSha256($"{parts[0]}.{parts[1]}", secret);
Assert.AreEqual(expectedSignature, receivedSignature);
}
/// <summary>
/// 场景测试:安全通信(RSA + AES 混合加密)
/// </summary>
[TestMethod]
public void Scenario_HybridEncryption()
{
// 场景:发送大量数据,使用混合加密(RSA 加密 AES 密钥,AES 加密数据)
// 1. 接收方生成 RSA 密钥对
var (publicKey, privateKey) = CryptoHelper.GenerateRsaKeys();
// 2. 发送方生成随机 AES 密钥
var aesKey = CryptoHelper.GenerateRandomString(32);
// 3. 使用 RSA 公钥加密 AES 密钥
var encryptedAesKey = CryptoHelper.RsaEncrypt(aesKey, publicKey);
// 4. 使用 AES 加密大量数据
var largeData = "This is a large amount of data that would be inefficient to encrypt with RSA directly...";
var encryptedData = CryptoHelper.AesEncrypt(largeData, aesKey);
// 5. 发送 encryptedAesKey 和 encryptedData...
// 6. 接收方使用 RSA 私钥解密 AES 密钥
var decryptedAesKey = CryptoHelper.RsaDecrypt(encryptedAesKey, privateKey);
// 7. 使用解密的 AES 密钥解密数据
var decryptedData = CryptoHelper.AesDecrypt(encryptedData, decryptedAesKey);
Assert.AreEqual(aesKey, decryptedAesKey);
Assert.AreEqual(largeData, decryptedData);
}
#endregion
}