.NET Core加解密實戰系列之——訊息摘要與數字簽名演算法

福祿網路技術團隊 發表於 2020-06-29

.NET Core加解密實戰系列之——訊息摘要與數字簽名演算法


簡介

加解密現狀,編寫此係列文章的背景:

  • 需要考慮系統環境相容性問題(Linux、Windows)
  • 語言互通問題(如C#、Java等)(加解密本質上沒有語言之分,所以原則上不存在互通性問題)
  • 網上資料版本不一、或不全面
  • .NET官方庫密碼演算法提供不全面,很難針對其他語言(Java)進行適配

本系列文章主要介紹如何在 .NET Core 中使用非對稱加密演算法、編碼演算法、訊息摘要演算法、簽名演算法、對稱加密演算法、國密演算法等一系列演算法,如有錯誤之處,還請大家批評指正。

本系列文章旨在引導大家能快速、輕鬆的瞭解接入加解密,乃至自主組合搭配使用BouncyCastle密碼術包中提供的演算法。

本文中程式碼示例僅列舉了比較常見的使用方式(BouncyCastle中提供的演算法遠不止這些),另外演算法DSA和ECDSA我們未在專案中使用過,程式碼組合性非常多,因此文中部分程式碼僅供參考。

本系列程式碼專案地址:https://github.com/fuluteam/ICH.BouncyCastle.git

上一篇文章《.NET Core加解密實戰系列之——RSA非對稱加密演算法》:https://www.cnblogs.com/fulu/p/13100471.html

功能依賴

BouncyCastle(https://www.bouncycastle.org/csharp) 是一個開放原始碼的輕量級密碼術包;它支援大量的密碼術演算法,它提供了很多 .NET Core標準庫沒有的演算法。

支援 .NET 4,.NET Standard 1.0-2.0,WP,Silverlight,MonoAndroid,Xamarin.iOS,.NET Core

功能 依賴
Portable.BouncyCastle Portable.BouncyCastle • 1.8.5

訊息摘要演算法

訊息摘要演算法分為三類:

  • MD 2/4/5(Message Digest Algorithm 2/4/5):訊息摘要演算法 MD2、MD4、MD5
  • SHA(Secure Hash Algorithm):安全雜湊演算法
  • MAC(Message Authentication Code):訊息認證碼

MD演算法

MD訊息摘要演算法,一種被廣泛使用的密碼雜湊函式,可以產生出一個128位(16位元組)的雜湊值(hash value),用於確保資訊傳輸完整一致。

家族發展史

  • MD2演算法:

    1989年,著名的非對稱演算法RSA發明人之一麻省理工學院教授羅納德·李維斯特開發了MD2演算法。這個演算法首先對資訊進行資料補位,使資訊的位元組長度是16的倍數。再以一個16位的檢驗和做為補充資訊追加到原資訊的末尾。最後根據這個新產生的資訊計算出一個128位的雜湊值,MD2演算法由此誕生。

  • MD4演算法:

    1990年,羅納德·李維斯特教授開發出較之MD2演算法有著更高安全性的MD4演算法。MD4演算法對後續訊息摘要演算法起到了推動作用,許多比較有名的訊息摘要演算法都是在MD4演算法的基礎上發展而來的,如MD5、SHA-1、RIPE-MD和HAVAL演算法等。

  • MD5演算法:

    1991年,繼MD4演算法後,羅納德·李維斯特教授開發了MD5演算法,用以取代MD4演算法。這套演算法的程式在 RFC 1321 標準中被加以規範。MD5演算法經MD2、MD3和MD4演算法發展而來,演算法複雜程度和安全強度大大提高,MD演算法的最終結果都是產生一個128位的資訊摘要。這也是MD系列演算法的特點。

    1996年,該演算法被證實存在弱點,可以被加以破解,對於需要高度安全性的資料,專家一般建議改用其他演算法,如SHA-2。

    2004年,證實MD5演算法無法防止碰撞(collision),因此不適用於安全性認證,如SSL公開金鑰認證或是數字簽名等用途。

應用場景

訊息摘要演算法是不可逆的,所以資訊摘要場景主要被用來驗證資訊的完整性,防止資訊被篡改,主要場景如下:

  • 驗籤:對要傳送的資料做MD5(一般加slat)MD5值和資料一同傳送,接收方接受資料做同樣的MD5計算,比較MD5值是否一致
  • 密碼保護:比如使用者密碼儲存上,一般都是儲存MD5值,更高一級的涉及是針對每個使用者生成一個隨機的slat,然後進MD5(passport + slat)計算,將這個值儲存到DB中

程式碼實現

MD5
public static class MD5
{
    /// <summary>
    /// 雜湊計算(使用BouncyCastle)
    /// </summary>
    /// <param name="s"></param>
    /// <returns></returns>
    public static byte[] Compute(string s)
    {
        if (string.IsNullOrEmpty(s))
        {
            throw new ArgumentNullException(nameof(s));
        }
        var digest = new MD5Digest();
        var resBuf = new byte[digest.GetDigestSize()];
        var input = Encoding.UTF8.GetBytes(s);

        digest.BlockUpdate(input, 0, input.Length);
        digest.DoFinal(resBuf, 0);

        return resBuf;
    }

    /// <summary>
    /// 雜湊計算(不使用BouncyCastle)
    /// </summary>
    /// <param name="s"></param>
    /// <returns></returns>
    public static byte[] Compute2(string s)
    {
        if (string.IsNullOrEmpty(s))
        {
            throw new ArgumentNullException(nameof(s));
        }

        using (var md5 = System.Security.Cryptography.MD5.Create())
        {
            return md5.ComputeHash(Encoding.UTF8.GetBytes(s));
        }
    }
}

示例程式碼

private static void MD5Sample()
{
    var s = "hello md5";
    Console.WriteLine(s);

    var resBytes1 = MD5.Compute("hello md5");

    var resBytes2 = MD5.Compute2("hello md5");

    var a1 = BitConverter.ToString(resBytes1).Replace("-", "");
    Console.WriteLine($"通過BitConverter.ToString轉換得到結果:{a1}");
    var a2 = Hex.ToHexString(resBytes1).ToUpper();
    Console.WriteLine($"通過Hex.ToHexString轉換得到結果:{a2}");

    var a3 = Hex.ToHexString(resBytes2).ToUpper();

    Console.WriteLine($"不使用BouncyCastle得到結果:{a3}");

    Console.WriteLine();
}

o_20061605561120200616135525

SHA演算法

安全雜湊演算法(英文:Secure Hash Algorithm,縮寫為SHA)是一個密碼雜湊函式家族,是FIPS所認證的安全雜湊演算法。

SHA家族的五個演算法,分別是SHA-1、SHA-224、SHA-256、SHA-384,和SHA-512,由美國國家安全域性(NSA)所設計,並由美國國家標準與技術研究院(NIST)釋出;是美國的政府標準。後四者有時並稱為SHA-2。SHA-1在許多安全協定中廣為使用,包括TLS和SSL、PGP、SSH、S/MIME和IPsec,曾被視為是MD5(更早之前被廣為使用的雜湊函式)的後繼者。但SHA-1的安全性如今被密碼學家嚴重質疑;雖然至今尚未出現對SHA-2有效的攻擊,它的演算法跟SHA-1基本上仍然相似;因此有些人開始發展其他替代的雜湊演算法。

2017年2月23日,Google公司公告宣稱他們與CWI Amsterdam合作共同建立了兩個有著相同的SHA-1值但內容不同的PDF檔案,這代表SHA-1演算法已被正式攻破

應用場景

目前SHA1的應用較為廣泛,主要應用於CA和數字證照中,另外在目前網際網路中流行的BT軟體中,也是使用SHA1來進行檔案校驗的。

程式碼實現

SHA1
public class SHA1
{
    /// <summary>
    /// 雜湊計算(使用BouncyCastle)
    /// </summary>
    public static byte[] Compute(string s)
    {
        if (string.IsNullOrEmpty(s))
        {
            throw new ArgumentNullException(nameof(s));
        }

        var digest = new Sha1Digest();
        var resBuf = new byte[digest.GetDigestSize()];
        var input = Encoding.UTF8.GetBytes(s);

        digest.BlockUpdate(input, 0, input.Length);
        digest.DoFinal(resBuf, 0);

        return resBuf;
    }

    /// <summary>
    /// 雜湊計算(不使用BouncyCastle)
    /// </summary>
    public static byte[] Compute2(string s)
    {
        if (string.IsNullOrEmpty(s))
        {
            throw new ArgumentNullException(nameof(s));
        }

        using (var sha1 = System.Security.Cryptography.SHA1.Create())
        {
            return sha1.ComputeHash(Encoding.UTF8.GetBytes(s));
        }
    }
}
SHA256
public class SHA256
{
    /// <summary>
    /// 雜湊計算(使用BouncyCastle)
    /// </summary>
    public static byte[] Compute1(string s)
    {
        if (string.IsNullOrEmpty(s))
        {
            throw new ArgumentNullException(nameof(s));
        }
        var digest = new Sha256Digest();
        var resBuf = new byte[digest.GetDigestSize()];
        var input = Encoding.UTF8.GetBytes(s);

        digest.BlockUpdate(input, 0, input.Length);
        digest.DoFinal(resBuf, 0);

        return resBuf;
    }

    /// <summary>
    /// 雜湊計算(不使用BouncyCastle)
    /// </summary>
    public static byte[] Compute2(string s)
    {
        if (string.IsNullOrEmpty(s))
        {
            throw new ArgumentNullException(nameof(s));
        }

        using (var sha256 = System.Security.Cryptography.SHA256.Create())
        {
            return sha256.ComputeHash(Encoding.UTF8.GetBytes(s));
        }
    }
}

示例程式碼

private static void SHA256Sample()
{
    var s = "hello sha-256";
    Console.WriteLine(s);

    Console.WriteLine($"使用BouncyCastle計算結果(轉Base64字串):{Base64.ToBase64String(SHA256.Compute1(s))}");

    Console.WriteLine($"不使用BouncyCastle計算結果(轉Base64字串):{Base64.ToBase64String(SHA256.Compute2(s))}");
}

20200629181910

MAC演算法

訊息認證碼演算法(英文:Message Authentication Codes,縮寫為MAC) 含有金鑰的雜湊函式演算法,相容了MD和SHA演算法的特性,並在此基礎上加上了金鑰。因此MAC演算法也經常被稱作HMAC演算法。訊息的雜湊值由只有通訊雙方知道的金鑰來控制。此時Hash值稱作MAC。

HMAC是金鑰相關的雜湊運算訊息認證碼(Hash-based Message Authentication Code)的縮寫,由H.Krawezyk,M.Bellare,R.Canetti於1996年提出的一種基於Hash函式和金鑰進行訊息認證的方法,並於1997年作為RFC2104被公佈,並在IPSec和其他網路協議(如SSL)中得以廣泛應用,現在已經成為事實上的Internet安全標準。它可以與任何迭代雜湊函式捆綁使用。

HMAC演算法的典型應用

HMAC演算法的一個典型應用是用在“挑戰/響應”(Challenge/Response)身份認證中,認證流程如下:

(1) 先由客戶端向伺服器發出一個驗證請求。
(2) 伺服器接到此請求後生成一個隨機數並通過網路傳輸給客戶端(此為挑戰)。
(3) 客戶端將收到的隨機數與自己的金鑰進行HMAC-SHA1運算並得到一個結果作為認證證據傳給伺服器(此為響應)。
(4) 與此同時,伺服器也使用該隨機數與儲存在伺服器資料庫中的該客戶金鑰進行HMAC-SHA1運算,如果伺服器的運算結果與客戶端傳回的響應結果相同,則認為客戶端是一個合法使用者 。

HMAC演算法的安全性

HMAC演算法引入了金鑰,其安全性已經不完全依賴於所使用的HASH演算法,安全性主要有以下幾點保證:

(1) 使用的金鑰是雙方事先約定的,第三方不可能知道。由上面介紹應用流程可以看出,作為非法截獲資訊的第三方,能夠得到的資訊只有作為“挑戰”的隨機數和作為“響應”的HMAC結果,無法根據這兩個資料推算出金鑰。由於不知道金鑰,所以無法仿造出一致的響應。
(2)在HMAC演算法的應用中,第三方不可能事先知道輸出(如果知道,不用構造輸入,直接將輸出送給伺服器即可)。
(3) HMAC演算法與一般的加密重要的區別在於它具有“瞬時”性,即認證只在當時有效,而加密演算法被破解後,以前的加密結果就可能被解密。

HMAC組合雜湊函式

與HMAC組合使用的演算法有HMac-MD5、HMac-SHA1、HMac-SHA256等等:


public class Algorithms
{
    public const string HMacSHA1="HMAC-SHA1";
    public const string HMacMD5="HMAC-MD5";
    public const string HMacMD4="HMAC-MD4";
    public const string HMacMD2="HMAC-MD2";
    public const string HMacSHA224="HMAC-SHA224";
    public const string HMacSHA256="HMAC-SHA256";
    public const string HMacSHA384="HMAC-SHA384";
    public const string HMacSHA512_224 = "HMAC-SHA512/224";
    public const string HMacSHA512_256="HMAC-SHA512/256";
    public const string HMacRIPEMD128="HMAC-RIPEMD128";
    public const string HMacRIPEMD160="HMAC-RIPEMD160";
    public const string HMacTIGER="HMAC-TIGER";
    public const string HMacKECCAK224="HMAC-KECCAK224";
    public const string HMacKECCAK256="HMAC-KECCAK256";
    public const string HMacKECCAK288="HMAC-KECCAK288";
    public const string HMacKECCAK384="HMAC-KECCAK384";
    public const string HMacSHA3512="HMAC-SHA3-512";
    public const string HMacGOST3411_2012256="HMAC-GOST3411-2012-256";
    public const string HMacGOST3411_2012_512="HMAC-GOST3411-2012-512";
    
    ......
}

程式碼實現

public class HMAC
{
    /// <summary>
    /// 生成金鑰KEY
    /// </summary>
    /// <param name="algorithm">密文演算法,參考Algorithms.cs中提供的HMac algorithm</param>
    /// <returns>金鑰KEY</returns>
    public static byte[] GeneratorKey(string algorithm)
    {
        var kGen = GeneratorUtilities.GetKeyGenerator(algorithm);
        return kGen.GenerateKey();
    }

    /// <summary>
    /// 雜湊計算
    /// </summary>
    /// <param name="data">輸入字串</param>
    /// <param name="key">金鑰KEY</param>
    /// <param name="algorithm">密文演算法,參考Algorithms.cs中提供的HMac algorithm</param>
    /// <returns>雜湊值</returns>
    public static byte[] Compute(string data, byte[] key, string algorithm)
    {
        if (string.IsNullOrEmpty(data))
        {
            throw new ArgumentNullException(nameof(data));
        }

        var keyParameter = new Org.BouncyCastle.Crypto.Parameters.KeyParameter(key);
        var input = Encoding.UTF8.GetBytes(data);
        var mac = MacUtilities.GetMac(algorithm);
        mac.Init(keyParameter);
        mac.BlockUpdate(input, 0, input.Length);
        return MacUtilities.DoFinal(mac);
    }

    /// <summary>
    /// 雜湊計算
    /// </summary>
    /// <param name="data">輸入字串</param>
    /// <param name="key">金鑰KEY</param>
    /// <param name="digest"></param>
    /// <returns>雜湊值</returns>
    public static byte[] Compute(string data, byte[] key, IDigest digest)
    {
        if (string.IsNullOrEmpty(data))
        {
            throw new ArgumentNullException(nameof(data));
        }

        var keyParameter = new Org.BouncyCastle.Crypto.Parameters.KeyParameter(key);
        var input = Encoding.UTF8.GetBytes(data);
        IMac mac = new Org.BouncyCastle.Crypto.Macs.HMac(digest);
        mac.Init(keyParameter);
        mac.BlockUpdate(input, 0, input.Length);
        return MacUtilities.DoFinal(mac);
    }
}
HMAC-MD5
public class HMACMD5
{
    /// <summary>
    /// 生成金鑰KEY
    /// </summary>
    public static byte[] GeneratorKey()
    {
        return HMAC.GeneratorKey(Algorithms.HMacMD5);
    }

    /// <summary>
    /// 雜湊計算(使用BouncyCastle)
    /// </summary>
    public static byte[] Compute(string data, byte[] key)
    {
        return HMAC.Compute(data, key, Algorithms.HMacMD5);
        //or
        //return HMAC.Compute(data, key, new MD5Digest());
    }

    /// <summary>
    /// 雜湊計算(不使用BouncyCastle)
    /// </summary>
    public static byte[] Compute2(string data, string key)
    {
        if (string.IsNullOrEmpty(data))
        {
            throw new ArgumentNullException(nameof(data));
        }

        if (string.IsNullOrEmpty(key))
        {
            throw new ArgumentNullException(nameof(key));
        }

        using (var hmacMd5 = new System.Security.Cryptography.HMACMD5(Encoding.UTF8.GetBytes(key)))
        {
            return hmacMd5.ComputeHash(Encoding.UTF8.GetBytes(data));
        }
    }
}
HMAC-SHA1
public class HMACSHA1
{
    /// <summary>
    /// 生成金鑰KEY
    /// </summary>
    /// <returns></returns>
    public static byte[] GeneratorKey()
    {
        return HMAC.GeneratorKey(Algorithms.HMacSHA1);
    }

    /// <summary>
    /// 雜湊計算(使用BouncyCastle)
    /// </summary>
    public static byte[] Compute(string data, byte[] key)
    {
        return HMAC.Compute(data, key, Algorithms.HMacSHA1);
        //or
        //return HMAC.Compute(data, key, new Sha1Digest());
    }

    /// <summary>
    /// 雜湊計算(不使用BouncyCastle)
    /// </summary>
    public static byte[] Compute2(string data, byte[] key)
    {
        if (string.IsNullOrEmpty(data))
        {
            throw new ArgumentNullException(nameof(data));
        }

        using (var hmacSha1 = new System.Security.Cryptography.HMACSHA1(key))
        {
            return hmacSha1.ComputeHash(Encoding.UTF8.GetBytes(data));
        }
    }
}
HMAC-SHA256
public class HMACSHA256
{
    /// <summary>
    /// 生成簽名
    /// </summary>
    public static byte[] GeneratorKey()
    {
        return HMAC.GeneratorKey(Algorithms.HMacSHA256);
    }

    /// <summary>
    /// 雜湊計算(使用BouncyCastle)
    /// </summary>
    public static byte[] Compute(string data, byte[] key)
    {

        return HMAC.Compute(data, key, Algorithms.HMacSHA256);

        //or
        //return HMAC.Compute(data, key, new Sha256Digest());
    }

    /// <summary>
    /// 雜湊計算(不使用BouncyCastle)
    /// </summary>
    public static byte[] Compute2(string data, byte[] key)
    {
        if (string.IsNullOrEmpty(data))
        {
            throw new ArgumentNullException(nameof(data));
        }

        using (var hmacSha256 = new System.Security.Cryptography.HMACSHA256(key))
        {
            return hmacSha256.ComputeHash(Encoding.UTF8.GetBytes(data));
        }
    }
}

示例程式碼

private static void HMacSha256Sample()
{
    var s = "hello hmac sha256";
    Console.WriteLine(s);
    var k = HMACSHA256.GeneratorKey();
    Console.WriteLine($"金鑰(十六進位制字串):{Hex.ToHexString(k)}");
    Console.WriteLine($"金鑰(Base64字串):{Base64.ToBase64String(k)}");

    var b1 = HMACSHA256.Compute(s, k);
    Console.WriteLine($"使用BouncyCastle計算結果(轉Base64字串):{Base64.ToBase64String(b1)}");
    Console.WriteLine($"使用BouncyCastle計算結果(轉十六進位制字串):{Hex.ToHexString(b1)}");

    var b2 = HMACSHA256.Compute2(s, k);

    Console.WriteLine($"不使用BouncyCastle計算結果(轉Base64字串):{Base64.ToBase64String(b2)}");
    Console.WriteLine($"不使用BouncyCastle計算結果(轉十六進位制字串):{Hex.ToHexString(b2)}");
}

20200629182003

數字簽名演算法

數字簽名是一個帶有金鑰的訊息摘要演算法,這個金鑰包括了公鑰和私鑰,用於驗證資料完整性、認證資料來源和抗否認,遵循OSI參考模型、私鑰簽名和公鑰驗證。也是非對稱加密演算法和訊息摘要演算法的結合體

FIPS 186-4規定了三種可用於資料保護的數字簽名生成和驗證技術:數字簽名演算法(DSA),橢圓曲線數字簽名演算法(ECDSA)和Rivest-Shamir Adelman演算法( RSA)。

Rivest-Shamir Adelman演算法( RSA)

RSA演算法是1977年由羅納德·裡維斯特(Ron Rivest)、阿迪·薩莫爾(Adi Shamir)和倫納德·阿德曼(Leonard Adleman)一起提出的。當時他們三人都在麻省理工學院工作。RSA就是他們三人姓氏開頭字母拼在一起組成的。

RSA是目前計算機密碼學中最經典演算法,也是目前為止使用最廣泛的數字簽名演算法,從提出到現在已近三十年,經歷了各種攻擊的考驗,逐漸為人們接受,普遍認為是目前最優秀的公鑰方案之一。

程式碼實現

SHA1WithRSA
public class SHA1WithRSA
{
    /// <summary>
    /// 生成簽名
    /// </summary>
    public static string GenerateSignature(string data, RSAParameters privateKey)
    {
        if (string.IsNullOrEmpty(data))
        {
            throw new ArgumentNullException(nameof(data));
        }

        using (var rsa = System.Security.Cryptography.RSA.Create())
        {
            rsa.ImportParameters(privateKey);
            return Base64.ToBase64String(rsa.SignData(Encoding.UTF8.GetBytes(data), HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1));
        }
    }

    /// <summary>
    /// 驗證簽名
    /// </summary>
    public static bool VerifySignature(string data, string sign, RSAParameters publicKey)
    {
        if (string.IsNullOrEmpty(data))
        {
            throw new ArgumentNullException(nameof(data));
        }

        if (string.IsNullOrEmpty(sign))
        {
            throw new ArgumentNullException(nameof(sign));
        }

        using (var rsa = System.Security.Cryptography.RSA.Create())
        {
            rsa.ImportParameters(publicKey);
            return rsa.VerifyData(Encoding.UTF8.GetBytes(data), Base64.Decode(sign), HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);
        }
    }

    /// <summary>
    /// 生成簽名
    /// </summary>
    public static string GenerateSignature(string data, AsymmetricKeyParameter privateKey)
    {
        var byteData = Encoding.UTF8.GetBytes(data);
        var normalSig = SignerUtilities.GetSigner("SHA1WithRSA");
        normalSig.Init(true, privateKey);
        normalSig.BlockUpdate(byteData, 0, data.Length);
        var normalResult = normalSig.GenerateSignature();
        return Base64.ToBase64String(normalResult);
    }

    /// <summary>
    /// 驗證簽名
    /// </summary>
    public static bool VerifySignature(string data, string sign, AsymmetricKeyParameter publicKey)
    {
        var signBytes = Base64.Decode(sign);
        var plainBytes = Encoding.UTF8.GetBytes(data);
        var verifier = SignerUtilities.GetSigner("SHA1WithRSA");
        verifier.Init(false, publicKey);
        verifier.BlockUpdate(plainBytes, 0, plainBytes.Length);

        return verifier.VerifySignature(signBytes);
    }
}
SHA256WithRSA
public class SHA256WithRSA
{
    /// <summary>
    /// 生成簽名
    /// </summary>
    public static string GenerateSignature(string data, RSAParameters privateKey)
    {
        if (string.IsNullOrEmpty(data))
        {
            throw new ArgumentNullException(nameof(data));
        }

        using (var rsa = System.Security.Cryptography.RSA.Create())
        {
            rsa.ImportParameters(privateKey);
            return Base64.ToBase64String(rsa.SignData(Encoding.UTF8.GetBytes(data), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1));
        }
    }

    /// <summary>
    /// 驗證簽名
    /// </summary>
    public static bool VerifySignature(string data, string sign, RSAParameters publicKey)
    {
        if (string.IsNullOrEmpty(data))
        {
            throw new ArgumentNullException(nameof(data));
        }

        if (string.IsNullOrEmpty(sign))
        {
            throw new ArgumentNullException(nameof(sign));
        }

        using (var rsa = System.Security.Cryptography.RSA.Create())
        {
            rsa.ImportParameters(publicKey);
            return rsa.VerifyData(Encoding.UTF8.GetBytes(data), Base64.Decode(sign), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
        }
    }

    /// <summary>
    /// 生成簽名
    /// </summary>
    public static string GenerateSignature(string data, AsymmetricKeyParameter parameter)
    {
        if (string.IsNullOrEmpty(data))
        {
            throw new ArgumentNullException(nameof(data));
        }

        if (parameter == null)
        {
            throw new ArgumentNullException(nameof(parameter));
        }

        var signer = SignerUtilities.GetSigner("SHA256WithRSA");
        signer.Init(true, parameter);
        var bytes = Encoding.UTF8.GetBytes(data);
        signer.BlockUpdate(bytes, 0, bytes.Length);
        return Base64.ToBase64String(signer.GenerateSignature());
    }

    /// <summary>
    /// 驗證簽名
    /// </summary>
    public static bool VerifySignature(string data, string sign, AsymmetricKeyParameter parameter)
    {
        if (string.IsNullOrEmpty(data))
        {
            throw new ArgumentNullException(nameof(data));
        }

        if (string.IsNullOrEmpty(sign))
        {
            throw new ArgumentNullException(nameof(sign));
        }

        if (parameter == null)
        {
            throw new ArgumentNullException(nameof(parameter));
        }

        var verifier = SignerUtilities.GetSigner("SHA256WithRSA");
        verifier.Init(false, parameter);
        var bytes = Encoding.UTF8.GetBytes(data);
        verifier.BlockUpdate(bytes, 0, bytes.Length);
        return verifier.VerifySignature(Base64.Decode(sign));
    }
}

示例程式碼

private static void SHA256WithRSA_Sample()
{
    var s = "hello sha256 with rsa";
    Console.WriteLine(s);

    var keyParameter = RSAKeyGenerator.Pkcs8(2048);

    Console.WriteLine("私鑰:");
    Console.WriteLine(keyParameter.PrivateKey);
    Console.WriteLine("公鑰:");
    Console.WriteLine(keyParameter.PublicKey);

    Console.WriteLine();

    Console.WriteLine("使用BouncyCastle:");

    var sign1 = SHA256WithRSA.GenerateSignature(s,
         RSAUtilities.GetAsymmetricKeyParameterFormAsn1PrivateKey(keyParameter.PrivateKey));
    Console.WriteLine("sign1:");
    Console.WriteLine(sign1);

    var verified1 = SHA256WithRSA.VerifySignature(s, sign1,
        RSAUtilities.GetAsymmetricKeyParameterFormPublicKey(keyParameter.PublicKey));

    Console.WriteLine("驗證結果:");
    Console.WriteLine(verified1 ? "signature verified" : "signature not verified");
    Console.WriteLine();

    Console.WriteLine("不使用BouncyCastle:");

    var sign2 = SHA256WithRSA.GenerateSignature(s,
        RSAUtilities.GetRsaParametersFormAsn1PrivateKey(keyParameter.PrivateKey));

    Console.WriteLine("sign2:");
    Console.WriteLine(sign2);

    var verified2 = SHA256WithRSA.VerifySignature(s, sign1,
        RSAUtilities.GetAsymmetricKeyParameterFormPublicKey(keyParameter.PublicKey));

    Console.WriteLine("驗證結果:");

    Console.WriteLine(verified2 ? "signature verified" : "signature not verified");
    Console.WriteLine();
}

20200629182142

數字簽名演算法(DSA)

Digital Signature Algorithm (DSA)是Schnorr和ElGamal簽名演算法的變種,被美國NIST作為DSS(DigitalSignature Standard)。DSA是基於整數有限域離散對數難題的,其安全性與RSA相比差不多。

DSA(用於數字簽名演算法)的簽名生成速度很快,驗證速度很慢,加密時更慢,但解密時速度很快

目前,最好使用RSA 2048位金鑰(也可以用4096位的RSA金鑰),商業RSA證照比DSA證照被更廣泛地部署。

OpenSSH 7.0及以上版本預設禁用了ssh-dss(DSA)公鑰演算法。官方沒有給出具體的解釋,但其中可能有OpenSSH,DSA金鑰位數生成的原因,同時生成簽名時隨機性差,可能會洩漏私鑰,且以現在機算機的算力,DSA 1024-bit已經實際上可破解,建議不使用。

一般來說,還是推薦大家使用RSA演算法。

程式碼實現

SHA1/DSA
/// <summary>
/// 生成簽名
/// </summary>
public static string GenerateSignature(string data, AsymmetricKeyParameter privateKey)
{
    var byteData = Encoding.UTF8.GetBytes(data);
    var normalSig = SignerUtilities.GetSigner("SHA1/DSA");
    normalSig.Init(true, privateKey);
    normalSig.BlockUpdate(byteData, 0, data.Length);
    var normalResult = normalSig.GenerateSignature();
    return Base64.ToBase64String(normalResult);
}

/// <summary>
/// 簽名驗證
/// </summary>
public static bool VerifySignature(string data, string sign, AsymmetricKeyParameter publicKey)
{
    var signBytes = Base64.Decode(sign);
    var plainBytes = Encoding.UTF8.GetBytes(data);
    var verifier = SignerUtilities.GetSigner("SHA1/DSA");
    verifier.Init(false, publicKey);
    verifier.BlockUpdate(plainBytes, 0, plainBytes.Length);

    return verifier.VerifySignature(signBytes);
}
SHA256/DSA
/// <summary>
/// 生成簽名
/// </summary>
public static string GenerateSignature(string data, AsymmetricKeyParameter parameter)
{
    if (string.IsNullOrEmpty(data))
    {
        throw new ArgumentNullException(nameof(data));
    }

    if (parameter == null)
    {
        throw new ArgumentNullException(nameof(parameter));
    }

    var signer = SignerUtilities.GetSigner("SHA256/DSA");
    signer.Init(true, parameter);
    var bytes = Encoding.UTF8.GetBytes(data);
    signer.BlockUpdate(bytes, 0, bytes.Length);
    return Base64.ToBase64String(signer.GenerateSignature());
}

/// <summary>
/// 驗證簽名
/// </summary>
public static bool VerifySignature(string data, string sign, AsymmetricKeyParameter parameter)
{
    if (string.IsNullOrEmpty(data))
    {
        throw new ArgumentNullException(nameof(data));
    }

    if (string.IsNullOrEmpty(sign))
    {
        throw new ArgumentNullException(nameof(sign));
    }

    if (parameter == null)
    {
        throw new ArgumentNullException(nameof(parameter));
    }

    var verifier = SignerUtilities.GetSigner("SHA256/DSA");
    verifier.Init(false, parameter);
    var bytes = Encoding.UTF8.GetBytes(data);
    verifier.BlockUpdate(bytes, 0, bytes.Length);
    return verifier.VerifySignature(Base64.Decode(sign));
}

示例程式碼

private static void SHA256WithDSA_Sample()
{
    var s = "hello dsa";
    Console.WriteLine(s);
    var keyParameter = DSAKeyGenerator.Generator();

    Console.WriteLine("私鑰:");
    Console.WriteLine(keyParameter.PrivateKey);
    Console.WriteLine("公鑰:");
    Console.WriteLine(keyParameter.PublicKey);

    Console.WriteLine();

    var sign = SHA256WithDSA.GenerateSignature(s,
        RSAUtilities.GetAsymmetricKeyParameterFormAsn1PrivateKey(keyParameter.PrivateKey));

    Console.WriteLine($"sign:{sign}");

    var verified = SHA256WithDSA.VerifySignature(s, sign,
        RSAUtilities.GetAsymmetricKeyParameterFormPublicKey(keyParameter.PublicKey));

    Console.WriteLine("驗證結果:");
    Console.WriteLine(verified ? "signature verified" : "signature not verified");
}

20200629182251

橢圓曲線數字簽名演算法(ECDSA)

橢圓曲線數字簽名演算法(ECDSA)是使用橢圓曲線密碼(ECC)對數字簽名演算法(DSA)的模擬。ECDSA於1999年成為ANSI標準,並於2000年成為IEEE和NIST標準。它在1998年既已為ISO所接受,並且包含它的其他一些標準亦在ISO的考慮之中。與普通的離散對數問題(discrete logarithm problem DLP)和大數分解問題(integer factorization problem IFP)不同,橢圓曲線離散對數問題(elliptic curve discrete logarithm problem ECDLP)沒有亞指數時間的解決方法。因此橢圓曲線密碼的單位位元強度要高於其他公鑰體制。

ECDSA是ECC與DSA的結合,整個簽名過程與DSA類似,所不一樣的是簽名中採取的演算法為ECC

ECC與RSA 相比,有以下的優點:

  • 相同金鑰長度下,安全效能更高,如160位ECC已經與1024位RSA、DSA有相同的安全強度。
  • 計算量小,處理速度快,在私鑰的處理速度上(解密和簽名),ECC遠 比RSA、DSA快得多。
  • 儲存空間佔用小 ECC的金鑰尺寸和系統引數與RSA、DSA相比要小得多, 所以佔用的儲存空間小得多。
  • 頻寬要求低使得ECC具有廣泛得應用前景。

程式碼實現

SHA1/ECDSA
/// <summary>
/// 生成簽名
/// </summary>
public static string GenerateSignature(string data, AsymmetricKeyParameter parameter)
{
    if (string.IsNullOrEmpty(data))
    {
        throw new ArgumentNullException(nameof(data));
    }

    if (parameter == null)
    {
        throw new ArgumentNullException(nameof(parameter));
    }

    var signer = SignerUtilities.GetSigner("SHA1/ECDSA");
    signer.Init(true, parameter);
    var bytes = Encoding.UTF8.GetBytes(data);
    signer.BlockUpdate(bytes, 0, bytes.Length);
    return Base64.ToBase64String(signer.GenerateSignature());
}

/// <summary>
/// 驗證簽名
/// </summary>
public static bool VerifySignature(string data, string sign, AsymmetricKeyParameter parameter)
{
    if (string.IsNullOrEmpty(data))
    {
        throw new ArgumentNullException(nameof(data));
    }

    if (string.IsNullOrEmpty(sign))
    {
        throw new ArgumentNullException(nameof(sign));
    }

    if (parameter == null)
    {
        throw new ArgumentNullException(nameof(parameter));
    }

    var verifier = SignerUtilities.GetSigner("SHA1/ECDSA");
    verifier.Init(false, parameter);
    var bytes = Encoding.UTF8.GetBytes(data);
    verifier.BlockUpdate(bytes, 0, bytes.Length);
    return verifier.VerifySignature(Base64.Decode(sign));
}
SHA256/ECDSA
/// <summary>
/// 生成簽名
/// </summary>
public static string GenerateSignature(string data, AsymmetricKeyParameter parameter)
{
    if (string.IsNullOrEmpty(data))
    {
        throw new ArgumentNullException(nameof(data));
    }

    if (parameter == null)
    {
        throw new ArgumentNullException(nameof(parameter));
    }

    var signer = SignerUtilities.GetSigner("SHA256/ECDSA");
    signer.Init(true, parameter);
    var bytes = Encoding.UTF8.GetBytes(data);
    signer.BlockUpdate(bytes, 0, bytes.Length);
    return Base64.ToBase64String(signer.GenerateSignature());
}

/// <summary>
/// 驗證簽名
/// </summary>
public static bool VerifySignature(string data, string sign, AsymmetricKeyParameter parameter)
{
    if (string.IsNullOrEmpty(data))
    {
        throw new ArgumentNullException(nameof(data));
    }

    if (string.IsNullOrEmpty(sign))
    {
        throw new ArgumentNullException(nameof(sign));
    }

    if (parameter == null)
    {
        throw new ArgumentNullException(nameof(parameter));
    }

    var verifier = SignerUtilities.GetSigner("SHA256/ECDSA");
    verifier.Init(false, parameter);
    var bytes = Encoding.UTF8.GetBytes(data);
    verifier.BlockUpdate(bytes, 0, bytes.Length);
    return verifier.VerifySignature(Base64.Decode(sign));
}

示例程式碼

private static void SHA256WithECDSA_Sample()
{
    var s = "hello ec dsa";
    Console.WriteLine(s);
    var keyParameter = ECDSAKeyGenerator.Generator();

    Console.WriteLine("私鑰:");
    Console.WriteLine(keyParameter.PrivateKey);
    Console.WriteLine("公鑰:");
    Console.WriteLine(keyParameter.PublicKey);

    var sign = SHA256WithECDSA.GenerateSignature(s,
        RSAUtilities.GetAsymmetricKeyParameterFormAsn1PrivateKey(keyParameter.PrivateKey));

    Console.WriteLine($"sign:{sign}");

    var verified = SHA256WithECDSA.VerifySignature(s, sign,
        RSAUtilities.GetAsymmetricKeyParameterFormPublicKey(keyParameter.PublicKey));

    Console.WriteLine("驗證結果:");
    Console.WriteLine(verified ? "signature verified" : "signature not verified");
}

20200629182338

下期預告

下一篇將介紹對稱加密演算法,敬請期待。。。
.NET Core加解密實戰系列之——訊息摘要與數字簽名演算法