消费者的卡号需要加密,密码需要salted 和 hashed.
密码学库:CryptographyLib
User类
public class User
{
public string Name { get; set; }
public string Salt { get; set; }
public string SaltedHashedPassword { get; set; }
public string[] Roles { get; set; }
}
Protector类:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Security.Cryptography;
using System.Security.Principal;
using System.Text;
using System.Xml.Linq;
using static System.Convert;
using static System.Console;
namespace Packt.Shared
{
public static class Protector
{
// salt大小必须至少为 8 个字节,我们将使用 16 个字节 size must be at least 8 bytes, we will use 16 bytes
private static readonly byte[] salt =
Encoding.Unicode.GetBytes("7BANANAS");//c#使用的是utf-16进行转码,单个字符占用2个字节
// iterations should be high enough to take at least 100ms to
// generate a Key and IV on the target machine. 50,000 iterations
// takes 131ms on my 3.3 GHz Dual-Core Intel Core i7 MacBook Pro.
// 迭代次数应该足够高,至少需要 100 毫秒
// 在目标机器上生成一个 Key 和 IV。 50,000 次迭代
// 在我的 3.3 GHz 双核英特尔酷睿 i7 MacBook Pro 上耗时 131 毫秒。
private static readonly int iterations = 50_000;
//加密 明文+密码
public static string Encrypt(
string plainText, string password)
{
byte[] encryptedBytes;//加密的字节
byte[] plainBytes = Encoding.Unicode.GetBytes(plainText);//普通字节
//创建用于执行对称算法的加密对象。
var aes = Aes.Create(); // abstract class factory method
var stopwatch = Stopwatch.StartNew();
//使用密码、盐和派生密钥的迭代次数初始化 System.Security.Cryptography.Rfc2898DeriveBytes 类的新实例。
var pbkdf2 = new Rfc2898DeriveBytes(
password, salt, iterations);
//返回此对象的伪随机密钥
aes.Key = pbkdf2.GetBytes(32); // 设置 256 位密钥set a 256-bit key
//返回此对象的伪随机密钥。
aes.IV = pbkdf2.GetBytes(16); // set a 128-bit IV
WriteLine("{0:N0} milliseconds to generate Key and IV using {1:N0} iterations.",
arg0: stopwatch.ElapsedMilliseconds,
arg1: iterations);
using (var ms = new MemoryStream())//使用初始化为零的可扩展容量初始化 System.IO.MemoryStream 类的新实例。
{ //CreateEncryptor 使用当前 System.Security.Cryptography.SymmetricAlgorithm.Key 创建对称加密器对象
// 属性和初始化向量 (System.Security.Cryptography.SymmetricAlgorithm.IV)。
using (var cs = new CryptoStream(
ms, aes.CreateEncryptor(), CryptoStreamMode.Write))//使用目标数据流、要使用的转换(对称加密对象)和流模式初始化 System.Security.Cryptography.CryptoStream 类的新实例
{
cs.Write(plainBytes, 0, plainBytes.Length);
}
encryptedBytes = ms.ToArray();//加密的字节
}
return Convert.ToBase64String(encryptedBytes);
}
//解密 密文+密码
public static string Decrypt(
string cryptoText, string password)
{
byte[] plainBytes;//明文字节
byte[] cryptoBytes = Convert.FromBase64String(cryptoText);//加密的字节
var aes = Aes.Create();
var pbkdf2 = new Rfc2898DeriveBytes(
password, salt, iterations);
aes.Key = pbkdf2.GetBytes(32);//秘钥
aes.IV = pbkdf2.GetBytes(16);//初始化向量
using (var ms = new MemoryStream())
{ //CreateDecryptor使用当前的 System.Security.Cryptography.SymmetricAlgorithm.Key 属性和初始化向量 (System.Security.Cryptography.SymmetricAlgorithm.IV) 创建对称解密器对象。
using (var cs = new CryptoStream(
ms, aes.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(cryptoBytes, 0, cryptoBytes.Length);
}
plainBytes = ms.ToArray();
}
return Encoding.Unicode.GetString(plainBytes);
}
//用户字典
private static Dictionary<string, User> Users =
new Dictionary<string, User>();
//注册 用户名+密码 +角色数组 . 不存储明文密码,存储salt和经过salt与hash计算的密码
public static User Register(string username, string password,
string[] roles = null)
{
// generate a random salt
var rng = RandomNumberGenerator.Create();
var saltBytes = new byte[16];
rng.GetBytes(saltBytes);//生成随机字节数组
var saltText = Convert.ToBase64String(saltBytes);//salt文本
// generate the salted and hashed password
var saltedhashedPassword = SaltAndHashPassword(
password, saltText);//经过salt和hash处理的密码
var user = new User
{
Name = username,
Salt = saltText, //salt
SaltedHashedPassword = saltedhashedPassword,//经过salt和hash处理的密码
Roles = roles
};
Users.Add(user.Name, user);
return user;
}
// check a user's password that is stored
// in the private static dictionary Users// 检查存储在私有静态字典 Users 中的用户密码
public static bool CheckPassword(string username, string password)
{
if (!Users.ContainsKey(username))
{
return false;
}
var user = Users[username];
return CheckPassword(username, password,
user.Salt, user.SaltedHashedPassword);
}
// check a user's password using salt and hashed password
//使用salt和hashed密码 检查用户密码
public static bool CheckPassword(string username, string password,
string salt, string hashedPassword)
{
// re-generate the salted and hashed password
var saltedhashedPassword = SaltAndHashPassword(
password, salt);
return (saltedhashedPassword == hashedPassword);
}
private static string SaltAndHashPassword(
string password, string salt)
{
var sha = SHA256.Create();//创建 System.Security.Cryptography.SHA256 的默认实现的实例。
var saltedPassword = password + salt;//
//将 8 位无符号整数数组转换为使用 base-64 数字编码的等效字符串表示形式。
return Convert.ToBase64String(
sha.ComputeHash(Encoding.Unicode.GetBytes(saltedPassword)));
}
public static string PublicKey;//公钥
//导出RSA参数到xml公钥
public static string ToXmlStringExt(
this RSA rsa, bool includePrivateParameters)
{
var p = rsa.ExportParameters(includePrivateParameters);//在派生类中重写时,导出 System.Security.Cryptography.RSAParameters。
XElement xml;
if (includePrivateParameters)
{
xml = new XElement("RSAKeyValue",
new XElement("Modulus", ToBase64String(p.Modulus)),
new XElement("Exponent", ToBase64String(p.Exponent)),
new XElement("P", ToBase64String(p.P)),
new XElement("Q", ToBase64String(p.Q)),
new XElement("DP", ToBase64String(p.DP)),
new XElement("DQ", ToBase64String(p.DQ)),
new XElement("InverseQ", ToBase64String(p.InverseQ)));
}
else
{
xml = new XElement("RSAKeyValue",
new XElement("Modulus", ToBase64String(p.Modulus)),
new XElement("Exponent", ToBase64String(p.Exponent)));
}
return xml?.ToString();
}
//从签名算法 参数xml导入RSA
public static void FromXmlStringExt(
this RSA rsa, string parametersAsXml)
{
var xml = XDocument.Parse(parametersAsXml);
var root = xml.Element("RSAKeyValue");
var p = new RSAParameters
{
Modulus = FromBase64String(root.Element("Modulus").Value),
Exponent = FromBase64String(root.Element("Exponent").Value)
};
if (root.Element("P") != null)
{
p.P = FromBase64String(root.Element("P").Value);
p.Q = FromBase64String(root.Element("Q").Value);
p.DP = FromBase64String(root.Element("DP").Value);
p.DQ = FromBase64String(root.Element("DQ").Value);
p.InverseQ = FromBase64String(root.Element("InverseQ").Value);
}
rsa.ImportParameters(p);
}
//生成数据的签名 先SHA256加密数据
public static string GenerateSignature(string data)
{
byte[] dataBytes = Encoding.Unicode.GetBytes(data);
var sha = SHA256.Create();
var hashedData = sha.ComputeHash(dataBytes);
var rsa = RSA.Create();
PublicKey = rsa.ToXmlStringExt(false); // exclude private key
return ToBase64String(rsa.SignHash(hashedData,
HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1));//RSASignaturePadding指定用于 RSA 签名创建或验证操作的填充模式和参数。
}
//验证签名
public static bool ValidateSignature(
string data, string signature)
{
byte[] dataBytes = Encoding.Unicode.GetBytes(data);
var sha = SHA256.Create();
var hashedData = sha.ComputeHash(dataBytes);//数据的哈希值
byte[] signatureBytes = FromBase64String(signature);//签名数据
var rsa = RSA.Create();
rsa.FromXmlStringExt(PublicKey);//导入RSA参数
//验证哈希值
return rsa.VerifyHash(hashedData, signatureBytes,
HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
public static byte[] GetRandomKeyOrIV(int size)
{
var r = RandomNumberGenerator.Create();
var data = new byte[size];
r.GetNonZeroBytes(data);
// data is an array now filled with
// cryptographically strong random bytes·
return data;
}
public static void LogIn(string username, string password)
{
if (CheckPassword(username, password))//用户名 密码验证通过
{//初始化 System.Security.Principal.GenericIdentity 类的新实例,表示具有指定名称和身份验证类型的用户。
var identity = new GenericIdentity(username, "PacktAuth");//用户标识:用户名,类型
//从用户标识和由该标识表示的用户所属的角色名称数组初始化 System.Security.Principal.GenericPrincipal 类的新实例。
var principal = new GenericPrincipal(
identity, Users[username].Roles);
System.Threading.Thread.CurrentPrincipal = principal;
}
}
}
}
加密Customer数据到xml
<?xml version="1.0" encoding="utf-8"?>
<customers>
<customer>
<name>Bob Smith</name>
<creditcard>EBLUHQBllivoCz84FU5XSM2XoTV7/VYbfnTjUgyKEEDdKsdBMGpTW8lfK2MmXLDJ</creditcard>
<password>Di5ZcpkcsZtPvJzJs2+b8A+4rrRTl7ooftVJuhKHww0=</password>
<salt>tYVQ3jSWp9rlS2bmwQ5ZFQ==</salt>
</customer>
<customer>
<name>Leslie Knope</name>
<creditcard>B6hEivJ6px9gKLZKIaZs0XKEsHRU/cKxWkzOBGv65IitaaJ/ZhJwbYJ8mclfEaX9</creditcard>
<password>6DbBMIAHDQ3idps1UXnP5RSJZfoEuSBlbToR+XdXnWM=</password>
<salt>EyxXnlgL55y6KDw/0jEFuw==</salt>
</customer>
</customers>
Customer类:
public class Customer
{
public string Name { get; set; }
public string CreditCard { get; set; }
public string Password { get; set; }
public string Salt { get; set; }
}
加密卡号:
using System.Collections.Generic;
using System.IO;
using System.Xml;
using Packt.Shared;
using static System.Console;
using static System.IO.Path;
using static System.Environment;
namespace Exercise02
{
class Program
{
static void Main(string[] args)
{
WriteLine("您必须输入密码才能加密文档中的敏感数据.");
WriteLine("稍后您必须输入相同的密码才能解密文档.");
Write("Password: ");
string password = ReadLine();
// define two example customers and
// note they have the same password
var customers = new List<Customer>
{
new Customer
{
Name = "Bob Smith",
CreditCard = "1234-5678-9012-3456",
Password = "Pa$$w0rd",
},
new Customer
{
Name = "Leslie Knope",
CreditCard = "8002-5265-3400-2511",
Password = "Pa$$w0rd",
}
};
//定义要写入的 XML 文件 define an XML file to write to
string xmlFile = Combine(CurrentDirectory,
"..", "protected-customers.xml");
var xmlWriter = XmlWriter.Create(xmlFile,
new XmlWriterSettings { Indent = true });//Indent 获取或设置一个值,该值指示是否缩进元素。
xmlWriter.WriteStartDocument();
xmlWriter.WriteStartElement("customers");
foreach (var customer in customers)
{
xmlWriter.WriteStartElement("customer");
xmlWriter.WriteElementString("name", customer.Name);
// to protect the credit card number we must encrypt it
xmlWriter.WriteElementString("creditcard",
Protector.Encrypt(customer.CreditCard, password));
// to protect the password we must salt and hash it
// and we must store the random salt used
var user = Protector.Register(customer.Name, customer.Password);
xmlWriter.WriteElementString("password", user.SaltedHashedPassword);
xmlWriter.WriteElementString("salt", user.Salt);
xmlWriter.WriteEndElement();
}
xmlWriter.WriteEndElement();
xmlWriter.WriteEndDocument();
xmlWriter.Close();
WriteLine();
WriteLine("Contents of the protected file:");
WriteLine();
WriteLine(File.ReadAllText(xmlFile));
}
}
}
解密卡号并登录
using System.Collections.Generic;
using System.IO;
using System.Xml;
using Packt.Shared;
using static System.Console;
using static System.IO.Path;
using static System.Environment;
using System.Security.Cryptography;
namespace Exercise03
{
class Program
{
static void Main(string[] args)
{
WriteLine("You must enter the correct password to decrypt the document.");
Write("Password: ");
string password = ReadLine();//加密密码
var customers = new List<Customer>();
// define an XML file to read from
string xmlFile = Combine(CurrentDirectory,
"..", "protected-customers.xml");
if (!File.Exists(xmlFile))
{
WriteLine($"{xmlFile} does not exist!");
return;
}
var xmlReader = XmlReader.Create(xmlFile,
new XmlReaderSettings { IgnoreWhitespace = true });//获取或设置一个值,该值指示是否忽略无关紧要的空白。
//读取xml
while (xmlReader.Read())
{
if (xmlReader.NodeType == XmlNodeType.Element && xmlReader.Name == "customer")
{
xmlReader.Read(); // move to <name>
string name = xmlReader.ReadElementContentAsString();
string creditcardEncrypted = xmlReader.ReadElementContentAsString();
string creditcard = null;
string errorMessage = null;
try
{
creditcard = Protector.Decrypt(creditcardEncrypted, password);//解密卡号
}
catch (CryptographicException)
{
errorMessage = $"Failed to decrypt {name}'s credit card.";
}
string passwordHashed = xmlReader.ReadElementContentAsString();//SaltedHashedPassword
string salt = xmlReader.ReadElementContentAsString();
customers.Add(new Customer
{
Name = name,
CreditCard = creditcard ?? errorMessage,
Password = passwordHashed,
Salt = salt
});
}
}
xmlReader.Close();
WriteLine();
int number = 0;
WriteLine(" {0,-20} {1,-20}",
arg0: "Name",
arg1: "Credit Card");
foreach (var customer in customers)
{
WriteLine("[{0}] {1,-20} {2,-20}",
arg0: number,
arg1: customer.Name,
arg2: customer.CreditCard);
number++;
}
WriteLine();
Write("Press the number of a customer to log in as: ");
//登录
string customerName = null;
try
{
number = int.Parse(ReadKey().KeyChar.ToString());
customerName = customers[number].Name;
}
catch
{
WriteLine();
WriteLine("Not a valid customer selection.");
return;
}
WriteLine();
Write($"Enter {customerName}'s password: ");
string attemptPassword = ReadLine();
//验证登录密码
if (Protector.CheckPassword(
username: customers[number].Name,
password: attemptPassword,
salt: customers[number].Salt,
hashedPassword: customers[number].Password))
{
WriteLine("Correct!");
}
else
{
WriteLine("Wrong!");
}
}
}
}