Đỉnh NGUYỄN

life's a journey not a destination

RSA – Tạo key pairs để mã hóa (encrypt) và giải mã (decrypt)

4 Comments


Trong mật mã học, RSA là một thuật toán mật mã hóa bất đối xứng (khóa công khai). Đây là thuật toán đầu tiên phù hợp với việc tạo ra chữ ký điện tử đồng thời với việc mã hóa. Nó đánh dấu một sự tiến bộ vượt bậc của lĩnh vực mật mã học trong việc sử dụng khóa công cộng. RSA đang được sử dụng phổ biến trong thương mại điện tử và được cho là đảm bảo an toàn với điều kiện độ dài khóa đủ lớn.

Thuật toán được Ron Rivest, Adi ShamirLen Adleman mô tả lần đầu tiên vào năm 1977 tại Học viện Công nghệ Massachusetts (MIT). Tên của thuật toán lấy từ 3 chữ cái đầu của tên 3 tác giả.

Thuật toán RSA có hai khóa: khóa công khai (hay khóa công cộng)khóa bí mật (hay khóa cá nhân). Mỗi khóa là những số cố định sử dụng trong quá trình mã hóa và giải mã. Khóa công khai được công bố rộng rãi cho mọi người và được dùng để mã hóa. Những thông tin được mã hóa bằng khóa công khai chỉ có thể được giải mã bằng khóa bí mật tương ứng. Nói cách khác, mọi người đều có thể mã hóa nhưng chỉ có người biết khóa cá nhân (bí mật) mới có thể giải mã được.

Ta có thể mô phỏng trực quan một hệ mật mã khoá công khai như sau: Bob muốn gửi cho Alice một thông tin mật mà Bob muốn duy nhất Alice có thể đọc được. Để làm được điều này, Alice gửi cho Bob một chiếc hộp có khóa đã mở sẵn và giữ lại chìa khóa. Bob nhận chiếc hộp, cho vào đó một tờ giấy viết thư bình thường và khóa lại (như loại khoá thông thường chỉ cần sập chốt lại, sau khi sập chốt khóa ngay cả Bob cũng không thể mở lại được-không đọc lại hay sửa thông tin trong thư được nữa). Sau đó Bob gửi chiếc hộp lại cho Alice. Alice mở hộp với chìa khóa của mình và đọc thông tin trong thư. Trong ví dụ này, chiếc hộp với khóa mở đóng vai trò khóa công khai, chiếc chìa khóa chính là khóa bí mật.

image

Cài đặt thuật toán mã hóa bất đối xứng RSA dùng C#:

using System;
using System.Security.Cryptography;
using System.Text;

namespace Security.Cryptography
{
    /// <summary>
    /// Performs asymmetric encryption and decryption using the implementation of
    /// the System.Security.Cryptography.RSA algorithm provided by the cryptographic
    /// service provider (CSP). This class cannot be inherited.
    /// Reference:
    /// http://jamiekurtz.com/2013/01/14/asp-net-web-api-security-basics/
    /// http://blogs.msdn.com/b/alejacma/archive/2008/10/23/how-to-generate-key-pairs-encrypt-and-decrypt-data-with-net-c.aspx
    /// http://www.technical-recipes.com/2013/using-rsa-to-encrypt-large-data-files-in-c/
    /// http://codebetter.com/johnvpetersen/2012/04/02/making-your-asp-net-web-apis-secure/
    /// </summary>
    public sealed class RSACryptography
    {

        #region Private Fields

        private const int KEY_SIZE = 2048; // The size of the RSA key to use in bits.
        private bool fOAEP = false;
        private RSACryptoServiceProvider rsaProvider = null;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the System.Security.Cryptography.RSACryptoServiceProvider
        /// class with the predefined key size and parameters.
        /// </summary>
        public RSACryptography()
        {
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Initializes a new instance of the System.Security.Cryptography.CspParameters class.
        /// </summary>
        /// <returns>An instance of the System.Security.Cryptography.CspParameters class.</returns>
        private CspParameters GetCspParameters()
        {
            // Create a new key pair on target CSP
            CspParameters cspParams = new CspParameters();
            cspParams.ProviderType = 1; // PROV_RSA_FULL 
            // cspParams.ProviderName; // CSP name
            // cspParams.Flags = CspProviderFlags.UseArchivableKey;
            cspParams.KeyNumber = (int)KeyNumber.Exchange;

            return cspParams;
        }

        /// <summary>  
        /// Gets the maximum data length for a given key  
        /// </summary>         
        /// <param name="keySize">The RSA key length  
        /// <returns>The maximum allowable data length</returns>  
        public int GetMaxDataLength()
        {
            if (fOAEP)
                return ((KEY_SIZE - 384) / 8) + 7;
            return ((KEY_SIZE - 384) / 8) + 37;
        }

        /// <summary>  
        /// Checks if the given key size if valid  
        /// </summary>         
        /// <param name="keySize">The RSA key length  
        /// <returns>True if valid; false otherwise</returns>  
        public static bool IsKeySizeValid()
        {
            return KEY_SIZE >= 384 &&
                   KEY_SIZE <= 16384 &&
                   KEY_SIZE % 8 == 0;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Generate a new RSA key pair.
        /// </summary>
        /// <param name="publicKey">An XML string containing ONLY THE PUBLIC RSA KEY.</param>
        /// <param name="privateKey">An XML string containing a PUBLIC AND PRIVATE RSA KEY.</param>
        public void GenerateKeys(out string publicKey, out string privateKey)
        {
            try
            {
                CspParameters cspParams = GetCspParameters();
                cspParams.Flags = CspProviderFlags.UseArchivableKey;

                rsaProvider = new RSACryptoServiceProvider(KEY_SIZE, cspParams);

                // Export public key
                publicKey = rsaProvider.ToXmlString(false);

                // Export private/public key pair 
                privateKey = rsaProvider.ToXmlString(true);
            }
            catch (Exception ex)
            {
                // Any errors? Show them
                throw new Exception("Exception generating a new RSA key pair! More info: " + ex.Message);
            }
            finally
            {
                // Do some clean up if needed
            }

        } // GenerateKeys method

        /// <summary>
        /// Encrypts data with the System.Security.Cryptography.RSA algorithm.
        /// </summary>
        /// <param name="publicKey">An XML string containing the public RSA key.</param>
        /// <param name="plainText">The data to be encrypted.</param>
        /// <returns>The encrypted data.</returns>
        public string Encrypt(string publicKey, string plainText)
        {
            if (string.IsNullOrWhiteSpace(plainText))
                throw new ArgumentException("Data are empty");

            int maxLength = GetMaxDataLength();
            if (Encoding.Unicode.GetBytes(plainText).Length > maxLength)
                throw new ArgumentException("Maximum data length is " + maxLength / 2);

            if (!IsKeySizeValid())
                throw new ArgumentException("Key size is not valid");

            if (string.IsNullOrWhiteSpace(publicKey))
                throw new ArgumentException("Key is null or empty");

            byte[] plainBytes = null;
            byte[] encryptedBytes = null;
            string encryptedText = "";

            try
            {
                CspParameters cspParams = GetCspParameters();
                cspParams.Flags = CspProviderFlags.NoFlags;

                rsaProvider = new RSACryptoServiceProvider(KEY_SIZE, cspParams);

                // [1] Import public key
                rsaProvider.FromXmlString(publicKey);

                // [2] Get plain bytes from plain text
                plainBytes = Encoding.Unicode.GetBytes(plainText);

                // Encrypt plain bytes
                encryptedBytes = rsaProvider.Encrypt(plainBytes, false);

                // Get encrypted text from encrypted bytes
                // encryptedText = Encoding.Unicode.GetString(encryptedBytes); => NOT WORKING
                encryptedText = Convert.ToBase64String(encryptedBytes);
            }
            catch (Exception ex)
            {
                // Any errors? Show them
                throw new Exception("Exception encrypting file! More info: " + ex.Message);
            }
            finally
            {
                // Do some clean up if needed
            }

            return encryptedText;

        } // Encrypt method

        /// <summary>
        /// Decrypts data with the System.Security.Cryptography.RSA algorithm.
        /// </summary>
        /// <param name="privateKey">An XML string containing a public and private RSA key.</param>
        /// <param name="encryptedText">The data to be decrypted.</param>
        /// <returns>The decrypted data, which is the original plain text before encryption.</returns>
        public string Decrypt(string privateKey, string encryptedText)
        {
            if (string.IsNullOrWhiteSpace(encryptedText))
                throw new ArgumentException("Data are empty");

            if (!IsKeySizeValid())
                throw new ArgumentException("Key size is not valid");

            if (string.IsNullOrWhiteSpace(privateKey))
                throw new ArgumentException("Key is null or empty");

            byte[] encryptedBytes = null;
            byte[] plainBytes = null;
            string plainText = "";

            try
            {
                CspParameters cspParams = GetCspParameters();
                cspParams.Flags = CspProviderFlags.NoFlags;

                rsaProvider = new RSACryptoServiceProvider(KEY_SIZE, cspParams);

                // [1] Import private/public key pair
                rsaProvider.FromXmlString(privateKey);

                // [2] Get encrypted bytes from encrypted text
                // encryptedBytes = Encoding.Unicode.GetBytes(encryptedText); => NOT WORKING
                encryptedBytes = Convert.FromBase64String(encryptedText);

                // Decrypt encrypted bytes
                plainBytes = rsaProvider.Decrypt(encryptedBytes, false);

                // Get decrypted text from decrypted bytes
                plainText = Encoding.Unicode.GetString(plainBytes);
            }
            catch (Exception ex)
            {
                // Any errors? Show them
                throw new Exception("Exception decrypting file! More info: " + ex.Message);
            }
            finally
            {
                // Do some clean up if needed
            }

            return plainText;

        } // Decrypt method

        #endregion

    }
}

Sử dụng thuật toán mã hóa RSA:

public string GenerateKeys()
{
    RSACryptography RSA = new RSACryptography();
    string publicKey, privateKey;

    // Generate RSA key pair
    RSA.GenerateKeys(out publicKey, out privateKey);

    string plainText = "93f99709-ce56-42a9-af7e-1d72c011c2dd";// Guid.NewGuid().ToString();

    // Encrypt
    string encryptedText = RSA.Encrypt(publicKey, plainText);

    // Decrypt
    string decryptedText = RSA.Decrypt(privateKey, encryptedText);

    return "<b>Token:</b> " + Server.HtmlEncode(plainText) + "<br />" + "<b>Public key:</b> " + Server.HtmlEncode(publicKey) + "<br />" + "<b>Private key:</b> " + Server.HtmlEncode(privateKey) + "<br />" + "<b>Encrypted text:</b> " + Server.HtmlEncode(encryptedText) + "<br />" + "<b>Decrypted text:</b> " + Server.HtmlEncode(decryptedText);
}

Tham khảo thêm cách cài đặt dùng C++: http://blogs.msdn.com/b/alejacma/archive/2008/01/28/how-to-generate-key-pairs-encrypt-and-decrypt-data-with-cryptoapi.aspx

Advertisements

Author: dinhnn

Senior software developer, a technical leader. You can be reached at via email to dinhnguyenngoc@gmail.com, via my blog at dinhnguyenngoc.wordpress.com, and on Twitter @dinhnguyenngoc.

4 thoughts on “RSA – Tạo key pairs để mã hóa (encrypt) và giải mã (decrypt)

  1. Anh ơi cho em hỏi mình lưu key với bản mã của lần chạy trước , lần sau mình dùng key đó để mã hóa bản mã đó được không anh?
    Nếu được thì mình phải lưu bản mã như thế nào để lần sau có chương trình giải mã được.
    Thanks!

    • Được em, mình dùng hàm GenerateKeys để tạo key pairs (public key & privte key) 1 lần. Sau đó lưu private key vào nơi an toàn, đưa public key cho người dùng mã hóa và khi cần giải mã thì dùng private key. Cách lưu private key tùy theo môi trường web hay desktop có thể chọn lưu trong web.config, trong plain text file, … miễn sau bảo mật không để mất private key là được.

  2. Anh ơi , khi chạy chương trình chọn giải mã/mã hóa mà bị thông báo Additional information: Bad Length, là do độ dài của dữ liệu hay do key . Nếu do key thì sửa sao để vẫn mã hóa / giải mã được? ( hiện tại em đang dùng c#)Em cảm ơn!

    • Bad length là do độ dài của key nha em. Em sử dụng đoạn mã của anh post bên trên hả? Trong môi trường Windows Form hay ASP.NET em? Cho anh biết dòng code gân nên lỗi này luôn nha. Vì độ dài đoạn code trên đã được tính toán đúng công thức để không sinh lỗi.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s