[C#] #11 使用 Bcrypt 密碼加密 - antqtech/KM GitHub Wiki

密碼加密與其重要性

密碼加密是一種將明文密碼轉換為難以解讀的密文的技術,用於保護使用者的資料不被未經授權的人訪問。這在資訊安全中至關重要,因為即使攻擊者獲得了加密後的密碼,也無法輕易將它還原為明文。

在這方面,密碼加密與雜湊往往同時被提及。密碼雜湊不同於加密,它是一種單向的函數,無法輕易反解。這使得它更適合用於存儲密碼,而不是雙向加密技術。如今,雜湊演算法常被用來確保密碼在資料庫中安全地存儲。

Bcrypt加密

Bcrypt 是一種基於雜湊的加密演算法,專為密碼加密設計。它採用了Blowfish加密演算法的變體,具有多種安全特性:

  1. 鹽值(Salt):每次Bcrypt生成密碼雜湊時,會自動加入一個隨機的鹽值,這確保即使兩個使用者的密碼相同,最終存儲的雜湊值也不同。這有效防止了彩虹表(Rainbow Table)攻擊。

  2. 可調整的成本因子:Bcrypt有一個可配置的「工作因子」,讓它可以隨著硬體性能提升而增加計算難度,從而增加破解時間。例如,工作因子越高,生成一個雜湊值需要的時間就越長,這也讓暴力破解變得更加困難。

  3. 抗暴力破解:Bcrypt被設計為對CPU密集型運算,因此它比其他簡單的雜湊演算法(如MD5、SHA-1)對暴力破解的防禦更有效。隨著計算能力的增長,Bcrypt的工作因子可以增加,使暴力破解所需的時間指數級增長。

Bcrypt相比MD5、SHA的優勢

  1. MD5與SHA-1的脆弱性:MD5(Message Digest Algorithm 5)和SHA-1(Secure Hash Algorithm 1)早期是很流行的雜湊演算法,但已被證實不夠安全。特別是對於碰撞攻擊,MD5和SHA-1在現代計算能力下已能較為容易地找到兩個不同的輸入對應同一個雜湊值,這是非常危險的。Bcrypt則不存在這個問題,且加入鹽值進一步增加其安全性。

  2. 可調整的安全性:Bcrypt的工作因子讓它在面對隨著時間增加的計算能力時,能通過增加運算難度保持高水準的安全性。相比之下,MD5和SHA的運算速度非常快,這對於暴力破解攻擊來說是一個優勢。

  3. 鹽值保護:MD5與SHA演算法本身不會內建加入鹽值,這意味著工程師需要自行實現鹽值的加入,且容易被忽略。而Bcrypt自動在每次雜湊生成時加入獨特的鹽值,無需額外操作,減少了實現錯誤的風險。

不使用密碼加密的危險

如果工程師直接將使用者的密碼以明文形式儲存在資料庫中,會帶來巨大的安全風險,包括但不限於:

  1. 資料洩露的風險:如果資料庫遭到駭客攻擊,攻擊者將能直接看到所有使用者的密碼,這會導致大量帳戶被盜用,尤其是很多人使用相同密碼來管理多個帳號。

  2. 信任損失:明文存儲密碼代表公司或平台對使用者資料的安全保護不夠,可能會破壞使用者對該平台的信任,進而影響公司聲譽。

  3. 違反法規:許多國家和地區的法律(例如歐盟的GDPR)對於使用者資料的保護有嚴格要求。以明文形式存儲密碼可能會違反這些法律,並導致法律責任或高額罰款。

  4. 內部威脅:如果密碼未加密,任何擁有資料庫存取權的內部員工都可以輕易讀取使用者的密碼,這增加了內部攻擊或資料被濫用的風險。

直接存明文相當於在大街上裸奔。

C# Bcrypt 實作(無介面)

  1. 第一步驟,打開套件管理工具下載BCrypt.Net-Next

  2. 第二步驟,實現它

    public class BCryptService
    {
     public string Encrypt(string original, string key = null, string iv = null)
     {
         // 使用 BCrypt 自動生成鹽值並哈希原始字串
         string saltedHash = BCrypt.Net.BCrypt.HashPassword(original);
         return saltedHash; // 只返回哈希值,BCrypt 自帶鹽
     }
    
     public bool Verify(string original, string encrypted)
     {
         // 使用 BCrypt 檢查原始字串是否與哈希值匹配
         return BCrypt.Net.BCrypt.Verify(original, encrypted);
     }
    }
    
  3. 依賴注入

    builder.Services.AddScoped<BCryptService>();
    
  4. 具體使用

    註冊時:
    new SqlParameter("@MPwd", _bcrypt.Encrypt(Pwd)),
    判斷密碼是否正確
    if (!_bcrypt.Verify(ReqLogin.MPwd, Acc[0].MPwd))
    {
        return _response.GenerateCustomBadRequestResponse("Err2", "密碼輸入錯誤!");
    }
    

C# 實作(有介面)

  1. 實現Encryption介面

    namespace SeeGo_API.Services.Interfaces
    {
        public interface IEncryption
        {
            string Encrypt(string original, string key = null, string iv = null);
            string Decrypt(string encrypted, string key = null, string iv = null);
            bool Verify(string original, string encrypted);
        }
    }
    
  2. 實現工廠介面

    namespace SeeGo_API.Services.Interfaces
    {
        public interface IEncryptionFactory
        {
            IEncryption CreateEncryptionService(string serviceType);
        }
    }
    
  3. 實現具體服務

    using System;
    using BCrypt.Net;
    using Org.BouncyCastle.Crypto.Generators;
    using SeeGo_API.Services.Interfaces;
    
    namespace SeeGo_API.Services
    {
        public class BCryptService : IEncryption
        {
            public string Encrypt(string original, string key = null, string iv = null)
            {
                // 使用 BCrypt 自動生成鹽值並哈希原始字串
                string saltedHash = BCrypt.Net.BCrypt.HashPassword(original);
                return saltedHash; // 只返回哈希值,BCrypt 自帶鹽
            }
    
            public string Decrypt(string encrypted, string key = null, string iv = null)
            {
                // 雜湊是單向的,因此這個方法不支持解密
                throw new NotImplementedException("Hashing is a one-way process and cannot be decrypted.");
            }
    
            public bool Verify(string original, string encrypted)
            {
                // 使用 BCrypt 檢查原始字串是否與哈希值匹配
                return BCrypt.Net.BCrypt.Verify(original, encrypted);
            }
        }
    }
    
  4. 實現工廠

    using SeeGo_API.Services.Interfaces;
    
    namespace SeeGo_API.Services
    {
        public class EncryptionFactory : IEncryptionFactory
        {
            private readonly IServiceProvider _serviceProvider;
    
            public EncryptionFactory(IServiceProvider serviceProvider)
            {
                _serviceProvider = serviceProvider;
            }
    
            public IEncryption CreateEncryptionService(string serviceType)
            {
                return serviceType switch
                {
                    "AES" => _serviceProvider.GetRequiredService<AesCryptoService>(),
                    "BCrypt" => _serviceProvider.GetRequiredService<BCryptService>(),
                    _ => throw new ArgumentException("Unknown encryption service type", nameof(serviceType))
                };
            }
        }
    }
    
  5. 依賴注入

    builder.Services.AddScoped<BCryptService>();
    builder.Services.AddScoped<AesCryptoService>();
    builder.Services.AddScoped<IEncryptionFactory, EncryptionFactory>();
    
  6. 在控制器使用工廠

    private readonly SeeGoDbConext _dbcontext;
    private readonly IWebHostEnvironment _env;
    private readonly IEncryption _aesCrypto;
    private readonly IEncryption _bcrypt;
    
    public AccountController(SeeGoDbConext dbcontext, IWebHostEnvironment env, IEncryptionFactory encryptionFactory)
    {
        _dbcontext = dbcontext;
        _env = env;
        _aesCrypto = encryptionFactory.CreateEncryptionService("AES");
        _bcrypt = encryptionFactory.CreateEncryptionService("BCrypt");
    }
    
  7. 具體使用

    註冊時:
    new SqlParameter("@MPwd", _bcrypt.Encrypt(Pwd)),
    判斷密碼是否正確
    if (!_bcrypt.Verify(ReqLogin.MPwd, Acc[0].MPwd))
    {
        return _response.GenerateCustomBadRequestResponse("Err2", "密碼輸入錯誤!");
    }