Personal
SP_INS_ENCRYPT_SINHVIEN
CREATE OR ALTER PROCEDURE SP_INS_ENCRYPT_SINHVIEN
@MASV NVARCHAR(20),
@HOTEN NVARCHAR(100),
@NGAYSINH DATETIME,
@DIACHI NVARCHAR(200),
@MALOP VARCHAR (20),
@TENDN NVARCHAR(100),
@MATKHAU VARCHAR(MAX)
AS
DECLARE @MATKHAU_bytes VARBINARY(MAX) = CONVERT(VARBINARY(25), @MATKHAU, 1);
IF NOT EXISTS(SELECT * FROM NHANVIEN WHERE TENDN = @TENDN)
INSERT INTO SINHVIEN VALUES(@MASV, @HOTEN, @NGAYSINH, @DIACHI, @MALOP, @TENDN, @MATKHAU_bytes);
GO
Giải thích: cần chuyển mật khẩu có dạng chuỗi sang VARBINARY
(lưu trong biến @MATKHAU_bytes
) bởi vì thuộc tính MATKHAU
lưu trong bảng nhân viên có kiểu là VARBINARY
.
Nhận xét: mật khẩu truyền qua đường truyền được hash, không thể bị lộ.
Thao tác hash mật khẩu được hiện thực ở trong code như sau:
using System.Security.Cryptography;
public static byte[] Hash(string method, string input)
{
if (method == "MD5")
return MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(input));
else if (method == "SHA1")
return SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(input));
else
return Array.Empty<byte>();
}
SP_INS_ENCRYPT_NHANVIEN
CREATE OR ALTER PROCEDURE SP_INS_ENCRYPT_NHANVIEN
@MANV VARCHAR (20),
@HOTEN NVARCHAR(100),
@EMAIL VARCHAR(20),
@LUONG VARCHAR(MAX),
@TENDN NVARCHAR(100),
@MATKHAU VARCHAR(MAX)
AS
DECLARE @LUONG_bytes VARBINARY(MAX) = CONVERT(VARBINARY(25), '0x' + @LUONG, 1);
DECLARE @MATKHAU_bytes VARBINARY(MAX) = CONVERT(VARBINARY(25), '0x' + @MATKHAU, 1);
IF NOT EXISTS(SELECT * FROM SINHVIEN WHERE TENDN = @TENDN)
INSERT INTO NHANVIEN VALUES(@MANV, @HOTEN, @EMAIL, @LUONG_bytes, @TENDN, @MATKHAU_bytes);
GO
Giải thích: tương tự SP_INS_ENCRYPT_SINHVIEN
, các đối số được mã hóa có dạng chuỗi cần phải được chuyển sang dạng VARBINARY
trước khi được thêm vào bảng.
Nhận xét:
- Hai thuộc tính
LUONG
vàMATKHAU
đã được mã hóa ở phía ứng dụng nên không bị lộ trên đường truyền. - Đối với lương được mã hóa bằng
AES 256
thì cơ sở dữ liệu không cần quan tâm đến việc quản lý khóa.
Trước khi thực hiện mã hóa ở trong ứng dụng thì cần khởi tạo biến _myAES
như sau:
private static AesCryptoServiceProvider _myAes = new AesCryptoServiceProvider
{
Key = Encoding.UTF8.GetBytes("20120356".PadLeft(32, '0')),
IV = new byte[16] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
Nhận xét: chuỗi khóa được đệm bằng các ký tự '0'
cho đến khi đủ 32 bytes và IV được gán giá trị là 16 bytes 0.
Thao tác mã hóa AES 256 được hiện thực ở trong code như sau:
using System.Security.Cryptography;
public static byte[] EncryptAES(string plainText)
{
byte[] encrypted;
// Create an encryptor to perform the stream transform.
ICryptoTransform encryptor = _myAes.CreateEncryptor();
// Create the streams used for encryption.
using MemoryStream msEncrypt = new MemoryStream();
using CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write);
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
//Write all data to the stream.
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
// Return the encrypted bytes from the memory stream.
return encrypted;
}
SP_SEL_ENCRYPT_NHANVIEN
Truy vấn dữ liệu của bảng nhân viên:
CREATE OR ALTER PROCEDURE SP_SEL_ENCRYPT_NHANVIEN
AS
SELECT * FROM NHANVIEN;
GO
Nhận xét: dữ liệu trả về của procedure là dữ liệu mã. Do đó mà phía ứng dụng cần phải thực hiện giải mã.
Đoạn code sau kiểm tra một cột ở trong DataTable
xem có phải là cột LUONG
hay không, nếu phải thì giải mã bằng phương thức DecryptAES
.
if (columnName == "LUONG")
{
string decryptedSalary = Utils.DecryptAES((byte[])employeeList.Rows[j][columnName]);
row[columnName] = decryptedSalary;
}
Code của phương thức DecryptAES
:
using System.Security.Cryptography;
public static string DecryptAES(byte[] cipherText)
{
string plaintext = "";
// Create a decryptor to perform the stream transform.
ICryptoTransform decryptor = _myAes.CreateDecryptor();
// Create the streams used for decryption.
using MemoryStream msDecrypt = new MemoryStream(cipherText);
using CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read);
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
// Read the decrypted bytes from the decrypting stream
// and place them in a string.
plaintext = srDecrypt.ReadToEnd();
}
return plaintext;
}
Summary
- Tất cả các thao tác mã hóa/giải mã đều được thực hiện ở phía ứng dụng. Do đó, dữ liệu quan trọng ở trên đường truyền không thể bị lộ.
- Ngoài ra, do cài đặt mã hóa ở phía ứng dụng nên không cần thực hiện trao đổi khóa giữa ứng dụng và DBMS. Dẫn đến, DBMS không cần phải quản lý khóa.
Group
SP_INS_PUBLIC_ENCRYPT_NHANVIEN
Tham số @LUONGCB
được mã hóa bằng RSA và @MK
được băm bằng SHA1, với khóa công khai được chia sẻ bởi ứng dụng.
create proc SP_INS_PUBLIC_ENCRYPT_NHANVIEN (
@MANV sysname, @HOTEN nvarchar(100), @EMAIL varchar(20),
@LUONGCB varchar(100), @TENDN nvarchar(100),
@MK varchar(100), @PUBKEY varchar(20)) as
Kiểu của tham số @LUONGCB
và @MK
là chuỗi nên ta cần chuyển sang dạng varbinary
:
begin
insert into NHANVIEN
values (@MANV, @HOTEN, @EMAIL, cast(@LUONGCB as varbinary(max)),
@TENDN, cast(@MK as varbinary(max)), @PUBKEY)
end
Giá trị các đối số truyền vào từ phía ứng dụng có thể là:
@MaNV=N'NV10',
@HoTen=N'Lê Minh Quân',
@Email=N'lmq@gmail.com',
@Luong=N'207985',
@Username=N'LMQ',
@pass=N'0x40BD001563085FC35165329EA1FF5C5ECBDBBEEF',
@pubkey=N'554917'
Với lương nhập vào bằng 1
và password nhập vào là 123
.
Ở trong code, giá trị lương nhập vào được mã hóa như sau:
RSAUtil a = new RSAUtil(item.PasswordEmployee);
a.generateKeys();
string salary = RSAUtil.Encrypt(item.RawSalaryEmployee, a.N, a.PublicKey);
Lớp RSAUtil
:
public RSAUtil(string pass)
{
Init(pass);
}
public void Init(string password)
{
int len = password.Length;
int pLen = len / 2;
int qLen = len - pLen;
string p = password.Substring(0, pLen);
string q = password.Substring(pLen, qLen);
//...
}
Nhận xét: hai giá trị p
và q
trong RSA được tạo ra dựa trên độ dài và giá trị của password, nên password cần có độ dài lớn hơn 1.
Riêng mật khẩu được mã hóa như sau:
SHA1Util.GetSHA1(item.PasswordEmployee))
Phương thức GetSHA1
:
using System.Security.Cryptography;
public static string GetSHA1(string input)
{
using (SHA1Managed sha1 = new SHA1Managed())
{
var hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(input));
var sb = new StringBuilder(hash.Length * 2);
foreach (byte b in hash)
{
// can be "x2" if you want lowercase
sb.Append(b.ToString("X2"));
}
return sb.ToString();
}
}
SP_LOGIN_NHANVIEN
Truy vấn ra danh sách public key của nhân viên dựa trên tên đăng nhập và mật khẩu truyền vào:
create proc SP_LOGIN_NHANVIEN
(@TENDN nvarchar(100), @MK varchar(100)) as
begin
select PUBKEY
from NHANVIEN
where TENDN = @TENDN and MATKHAU = cast(@MK as varbinary(max))
end
Ứng dụng gọi procedure này trong phương thức IsExist
:
public string IsExist(string username, string password)
{
object count = "";
string query = "exec SP_LOGIN_NHANVIEN '" + username + "', '0x" + Util.SHA1Util.GetSHA1(password) + "'";
//...
if (count == null) return "";
return count.ToString();
}
Phương thức này thực chất được dùng để kiểm tra sự tồn tại của một nhân viên bất kỳ dựa trên username và password. Nếu tồn tại thì nó sẽ lưu lại public key trả về từ procedure:
string pubkey = _repository.IsExist(_view.Username, _view.Password);
Và khởi tạo user cho phiên đăng nhập:
User user = new User(_view.Username, _view.Password, pubkey);
SP_SEL_NHANVIEN
Truy vấn ra danh sách nhân viên, với thuộc tính lương ở trạng thái mã hóa:
create proc SP_SEL_NHANVIEN as
begin
select MANV, HOTEN, EMAIL, cast(LUONG as varchar(100)) as LUONG
from NHANVIEN
end
Ứng dụng tái tạo lại private key của user dựa trên public key tìm được thông qua SP_LOGIN_NHANVIEN
để tiến hành giải mã:
string pub = admin.PublicKey;
RSAUtil a = new RSAUtil(admin.Password);
a.PublicKey = BigInteger.Parse(pub);
a.createPrivateKey();
Phương thức createPrivateKey
:
public void createPrivateKey()
{
privateKey = ModInverse(publicKey, _phi);
}
Với thuộc tính _phi
đã được tạo ra ở trong phương thức Init
ở trên:
public void Init(string password){
// ...
BigInteger phi = (_p - 1) * (_q - 1);
// ...
}
Sau đó dùng private key có được để giải mã lương của user đang đăng nhập:
RawSalaryEmployee = RSAUtil.Decrypt(reader["LUONG"].ToString(), a.N, a.PrivateKey)
SaveEmployee
Đoạn code chỉnh sửa thông tin nhân viên:
if (_view.isEdit)
{
model.IdEmployee = _view.idEmployee;
model.NameEmployee = _view.nameEmployee;
model.EmailEmployee = _view.emailEmployee;
model.Salary = AES256Util.EncryptStringToBytes_Aes(_view.salaryEmployee, "19120659");
new Common.ModelDataValidation().Validate(model);
_employeeRepository.Update(model);
_view.message = "Employee updated";
}
Có thể thấy, lương ban đầu lúc insert được mã hóa bằng RSA, nhưng lúc chỉnh sửa lại mã hóa bằng AES, mà khóa của AES lại là chuỗi 19120659
gán cứng cố định với mọi người dùng!
Summary
Ưu điểm:
- Tất cả các thông tin quan trọng đều được mã hóa nên không bị lộ trên đường truyền.
- Đa số các thao tác đều xác thực mật khẩu của người dùng đang đăng nhập.
Nhược điểm:
- Lương được mã hóa bằng RSA, các lỗ hổng và bug:
- Cặp khóa (cụ thể là hai số p và q) của hệ mã được sinh ra dựa trên mật khẩu của người dùng: không an toàn và chức năng đổi mật khẩu rất khó thực hiện.
- Nếu mật khẩu rỗng hoặc có ít hơn 2 ký tự thì có thể làm đơ chương trình.
- RSA được cài đặt một cách thủ công: không đảm bảo an toàn.
- Để giải mã, cần tái tạo lại khóa bí mật từ mật khẩu. Tuy nhiên, trong trường hợp mật khẩu dài thì cần phải tính toán rất nhiều. Mà RSA vốn là một thuật toán chậm chạp, do đó, cách làm này là không tối ưu.
- Lúc cập nhật lương thì lại dùng mã hóa bằng AES thay vì RSA, điều này gây ra lỗi dữ liệu dẫn đến lỗi chương trình.