天天看點

相容javascript和C#的RSA加密解密算法,對web送出的資料進行加密傳輸

  Web應用中往往涉及到敏感的資料,由于HTTP協定以明文的形式與伺服器進行互動,是以可以通過截獲請求的資料包進行分析來盜取有用的資訊。雖然https可以對傳輸的資料進行加密,但是必須要申請證書(一般都是收費的),成本較高。那麼問題來了,如果對web送出的敏感資料進行加密呢?web應用中,前端的資料處理和互動基本上都是靠javascript來完成,背景的邏輯處理可以C#(java)等進行處理。

  微軟的C#中雖然有RSA算法,但是格式和OpenSSL生成的公鑰/私鑰檔案格式并不相容。這個也給貫通前背景的RSA加密解密帶來了難度。為了相容OpenSSL生成的公鑰/私鑰檔案格式,貫通javascript和C#的RSA加密解密算法,必須對C#内置的方法進行再度封裝。

    下面以登入為例,使用者在密碼框輸入密碼後,javascript發送ajax請求時,對密碼先進行rsa加密後再發送,伺服器接收到加密後的密碼後,先對其進行解密, 然後再驗證登入是否成功。

  1  為了進行RSA加密解密,首先需要用openssl生成一對公鑰和私鑰(沒有的先下載下傳openssl):

1) 打開openssl.exe檔案,輸入

 genrsa -out openssl_rsa_priv.pem 1024

相容javascript和C#的RSA加密解密算法,對web送出的資料進行加密傳輸

此指令在openssl.exe同目錄下生成openssl_rsa_private_key.pem檔案。

  2) 生成公鑰

rsa  -in openssl_rsa__private.pem -pubout -out openssl_rsa__public.pem

相容javascript和C#的RSA加密解密算法,對web送出的資料進行加密傳輸
  以上指令會建立如下的檔案:
相容javascript和C#的RSA加密解密算法,對web送出的資料進行加密傳輸
這個檔案可以用文本編輯器進行打開,檢視内容。

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC0w036ClSD0LvxPROMun0u022R
OJlZE6P3m+gjq3gpi4n7lo8jhTqMqgccDbVJqnIfMzWS9O3lnlQXWTxJ3B4XJ52F
AcriY5brOXUVgBLx5QMHLLd1gtJnmG4i7r4ytgX7XVKRnojR6zca1YnS0lbGGDF1
CGllB1riNrdksSQP+wIDAQAB
-----END PUBLIC KEY-----      
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQC0w036ClSD0LvxPROMun0u022ROJlZE6P3m+gjq3gpi4n7lo8j
hTqMqgccDbVJqnIfMzWS9O3lnlQXWTxJ3B4XJ52FAcriY5brOXUVgBLx5QMHLLd1
gtJnmG4i7r4ytgX7XVKRnojR6zca1YnS0lbGGDF1CGllB1riNrdksSQP+wIDAQAB
AoGAIOyl6lIxXKULZoBKbEqXfIz0GwxlGg1ywyn5mW2lAGQzKMken0ioBnD9xIVW
rOlHyhkIvBCyuC0jgfE2Avn93MlB3j0WRuXMFlJpCBlEklMilO9Zgmwl+vTB3VZb
8VzdrEEEUBio7LWP/KvSo+IFlNjDTKgAczbLTwAmj4w6g0ECQQDm4yxPdxcU2ywZ
7PyjIMM9qnSah9KcrjU8gjEyHsUpgTjhw1cx7Peo+vRiHqxDy1yaSu1BlwRR52pC
jKNnl0QhAkEAyGx3NxEIiLk2oXGGbIMZ4P6geC8gYu01BiRNWVf0Yi7+sCH68eUP
oI+G5bJ8bvzXpvHjQi0s2OlRfct/qtPQmwJBALa+2DONbxdy4lUi3lO/esk0QVaO
aoTY3gomggnJkQRo4zzOABXkGaIF/6gp3u9J5uG4rFFd1m19XP2Pk0ZK1AECQBYi
lJAKW4zuF7CA3z3AxOzqckKTwdnrJL4G6FwDsMPfONWvCw4IJE+xSk64BbIkTpTr
hhPa9WcHba6c+P6e4h0CQQDWeGMMpkqPG/w4afNCGmvRnM8vNkGUAmDGvCsfkTID
ijpKl5SD55hPHsWE5rsv1TLUpkWtrFBcg61bHwMUP3cv
-----END RSA PRIVATE KEY-----      

2 用jsencrypt對密碼進行加密:

首先需要導入js封包件:

<script src="dist/js/jsencrypt.js"></script>      

然後編寫JS加密算法,示例如下:

var encrypt = new JSEncrypt();
var pubkey = "-----BEGIN PUBLIC KEY----- \
    MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAj0dPnBMf3Z4VT1B8Ee6bjKNs \
    hlYj7xvGijAa8RCdmGR7mrtrExnk8mdUlwdcS05gc4SSFOyWJcYtKUHpWn8/pkS0 \
    vgGOl9Bzn0Xt9hiqTb3pZAfykNrMDGZMgJgfD6KTnfzVUAOupvxjcGkcoj6/vV5I \
    eMcx8mT/z3elfsDSjQIDAQAB \
    -----END PUBLIC KEY-----";
encrypt.setPublicKey(pubkey);
var encrypted = encrypt.encrypt($('#txtpwd').val());
//console.log(encrypted);
$.ajax({
    type: "POST",
    url: "http://localhost:24830/services/rsa_pem.ashx",
    data: { "pwd": encrypted },
    dataType: "Json",
    error: function (xhr, status, error) {
           // alert(error);
            $("#txtInfo").text(' 請求伺服器失敗!');
            $(that).text('登 錄');
            $(that).attr('disabled', false);
    },
    success: function (json) {

        if (uid == "admin" && json.data=="000") {
            window.location.href = "index.html";
        }
        else {
            $("#txtInfo").text(' 使用者名或者密碼錯誤!');
            $(that).text('登 錄');
            $(that).attr('disabled', false);
        }
    }
});      

3 背景用C#進行解密

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace CMCloud.SaaS
{
    public class RSACryptoService
    {
        private RSACryptoServiceProvider _privateKeyRsaProvider;
        private RSACryptoServiceProvider _publicKeyRsaProvider;

        /// <summary>
        /// RSA解密
        /// </summary>
        /// <param name="cipherText"></param>
        /// <returns></returns>
        public string Decrypt(string cipherText)
        {
            if (_privateKeyRsaProvider == null)
            {
                throw new Exception("_privateKeyRsaProvider is null");
            }
            return Decrypt2(cipherText);
        }
        /// <summary>
        /// RSA加密
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        public string Encrypt(string text)
        {
            if (_publicKeyRsaProvider == null)
            {
                throw new Exception("_publicKeyRsaProvider is null");
            }
            return Encrypt2(text);
            //return Convert.ToBase64String(_publicKeyRsaProvider.Encrypt(Encoding.UTF8.GetBytes(text), false));

        }
        private string Encrypt2(string text)
        {


            Byte[] PlaintextData = Encoding.UTF8.GetBytes(text);
            int MaxBlockSize = _publicKeyRsaProvider.KeySize / 8 - 11;//加密塊最大長度限制

            if (PlaintextData.Length <= MaxBlockSize)
            {
                return Convert.ToBase64String(_publicKeyRsaProvider.Encrypt(PlaintextData, false));
            }
            else
            {
                using (MemoryStream PlaiStream = new MemoryStream(PlaintextData))
                using (MemoryStream CrypStream = new MemoryStream())
                {
                    Byte[] Buffer = new Byte[MaxBlockSize];
                    int BlockSize = PlaiStream.Read(Buffer, 0, MaxBlockSize);

                    while (BlockSize > 0)
                    {
                        Byte[] ToEncrypt = new Byte[BlockSize];
                        Array.Copy(Buffer, 0, ToEncrypt, 0, BlockSize);

                        Byte[] Cryptograph = _publicKeyRsaProvider.Encrypt(ToEncrypt, false);
                        CrypStream.Write(Cryptograph, 0, Cryptograph.Length);

                        BlockSize = PlaiStream.Read(Buffer, 0, MaxBlockSize);
                    }

                    return Convert.ToBase64String(CrypStream.ToArray(), Base64FormattingOptions.None);
                }
            }




        }

        private string Decrypt2(string ciphertext)
        {


            Byte[] CiphertextData = Convert.FromBase64String(ciphertext);
            int MaxBlockSize = _privateKeyRsaProvider.KeySize / 8;    //解密塊最大長度限制

            if (CiphertextData.Length <= MaxBlockSize)
                return System.Text.Encoding.UTF8.GetString(_privateKeyRsaProvider.Decrypt(CiphertextData, false));

            using (MemoryStream CrypStream = new MemoryStream(CiphertextData))
            using (MemoryStream PlaiStream = new MemoryStream())
            {
                Byte[] Buffer = new Byte[MaxBlockSize];
                int BlockSize = CrypStream.Read(Buffer, 0, MaxBlockSize);

                while (BlockSize > 0)
                {
                    Byte[] ToDecrypt = new Byte[BlockSize];
                    Array.Copy(Buffer, 0, ToDecrypt, 0, BlockSize);

                    Byte[] Plaintext = _privateKeyRsaProvider.Decrypt(ToDecrypt, false);
                    PlaiStream.Write(Plaintext, 0, Plaintext.Length);

                    BlockSize = CrypStream.Read(Buffer, 0, MaxBlockSize);
                }

                return System.Text.Encoding.UTF8.GetString(PlaiStream.ToArray());
            }
        }
        public RSACryptoService(string privateKey, string publicKey = null)
        {
            if (!string.IsNullOrEmpty(privateKey))
            {
                _privateKeyRsaProvider = CreateRsaProviderFromPrivateKey(privateKey);
            }

            if (!string.IsNullOrEmpty(publicKey))
            {
                _publicKeyRsaProvider = CreateRsaProviderFromPublicKey(publicKey);
            }
        }



        private RSACryptoServiceProvider CreateRsaProviderFromPrivateKey(string privateKey)
        {
            var privateKeyBits = System.Convert.FromBase64String(privateKey);

            var RSA = new RSACryptoServiceProvider();
            var RSAparams = new RSAParameters();

            using (BinaryReader binr = new BinaryReader(new MemoryStream(privateKeyBits)))
            {
                byte bt = 0;
                ushort twobytes = 0;
                twobytes = binr.ReadUInt16();
                if (twobytes == 0x8130)
                    binr.ReadByte();
                else if (twobytes == 0x8230)
                    binr.ReadInt16();
                else
                    throw new Exception("Unexpected value read binr.ReadUInt16()");

                twobytes = binr.ReadUInt16();
                if (twobytes != 0x0102)
                    throw new Exception("Unexpected version");

                bt = binr.ReadByte();
                if (bt != 0x00)
                    throw new Exception("Unexpected value read binr.ReadByte()");

                RSAparams.Modulus = binr.ReadBytes(GetIntegerSize(binr));
                RSAparams.Exponent = binr.ReadBytes(GetIntegerSize(binr));
                RSAparams.D = binr.ReadBytes(GetIntegerSize(binr));
                RSAparams.P = binr.ReadBytes(GetIntegerSize(binr));
                RSAparams.Q = binr.ReadBytes(GetIntegerSize(binr));
                RSAparams.DP = binr.ReadBytes(GetIntegerSize(binr));
                RSAparams.DQ = binr.ReadBytes(GetIntegerSize(binr));
                RSAparams.InverseQ = binr.ReadBytes(GetIntegerSize(binr));
            }

            RSA.ImportParameters(RSAparams);
            return RSA;
        }

        private int GetIntegerSize(BinaryReader binr)
        {
            byte bt = 0;
            byte lowbyte = 0x00;
            byte highbyte = 0x00;
            int count = 0;
            bt = binr.ReadByte();
            if (bt != 0x02)
                return 0;
            bt = binr.ReadByte();

            if (bt == 0x81)
                count = binr.ReadByte();
            else
                if (bt == 0x82)
            {
                highbyte = binr.ReadByte();
                lowbyte = binr.ReadByte();
                byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
                count = BitConverter.ToInt32(modint, 0);
            }
            else
            {
                count = bt;
            }

            while (binr.ReadByte() == 0x00)
            {
                count -= 1;
            }
            binr.BaseStream.Seek(-1, SeekOrigin.Current);
            return count;
        }

        private RSACryptoServiceProvider CreateRsaProviderFromPublicKey(string publicKeyString)
        {
            // encoded OID sequence for  PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
            byte[] SeqOID = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
            byte[] x509key;
            byte[] seq = new byte[15];
            int x509size;

            x509key = Convert.FromBase64String(publicKeyString);
            x509size = x509key.Length;

            // ---------  Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob  ------
            using (MemoryStream mem = new MemoryStream(x509key))
            {
                using (BinaryReader binr = new BinaryReader(mem))  //wrap Memory Stream with BinaryReader for easy reading
                {
                    byte bt = 0;
                    ushort twobytes = 0;

                    twobytes = binr.ReadUInt16();
                    if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
                        binr.ReadByte();    //advance 1 byte
                    else if (twobytes == 0x8230)
                        binr.ReadInt16();   //advance 2 bytes
                    else
                        return null;

                    seq = binr.ReadBytes(15);       //read the Sequence OID
                    if (!CompareBytearrays(seq, SeqOID))    //make sure Sequence for OID is correct
                        return null;

                    twobytes = binr.ReadUInt16();
                    if (twobytes == 0x8103) //data read as little endian order (actual data order for Bit String is 03 81)
                        binr.ReadByte();    //advance 1 byte
                    else if (twobytes == 0x8203)
                        binr.ReadInt16();   //advance 2 bytes
                    else
                        return null;

                    bt = binr.ReadByte();
                    if (bt != 0x00)     //expect null byte next
                        return null;

                    twobytes = binr.ReadUInt16();
                    if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
                        binr.ReadByte();    //advance 1 byte
                    else if (twobytes == 0x8230)
                        binr.ReadInt16();   //advance 2 bytes
                    else
                        return null;

                    twobytes = binr.ReadUInt16();
                    byte lowbyte = 0x00;
                    byte highbyte = 0x00;

                    if (twobytes == 0x8102) //data read as little endian order (actual data order for Integer is 02 81)
                        lowbyte = binr.ReadByte();  // read next bytes which is bytes in modulus
                    else if (twobytes == 0x8202)
                    {
                        highbyte = binr.ReadByte(); //advance 2 bytes
                        lowbyte = binr.ReadByte();
                    }
                    else
                        return null;
                    byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };   //reverse byte order since asn.1 key uses big endian order
                    int modsize = BitConverter.ToInt32(modint, 0);

                    int firstbyte = binr.PeekChar();
                    if (firstbyte == 0x00)
                    {   //if first byte (highest order) of modulus is zero, don't include it
                        binr.ReadByte();    //skip this null byte
                        modsize -= 1;   //reduce modulus buffer size by 1
                    }

                    byte[] modulus = binr.ReadBytes(modsize);   //read the modulus bytes

                    if (binr.ReadByte() != 0x02)            //expect an Integer for the exponent data
                        return null;
                    int expbytes = (int)binr.ReadByte();        // should only need one byte for actual exponent data (for all useful values)
                    byte[] exponent = binr.ReadBytes(expbytes);

                    // ------- create RSACryptoServiceProvider instance and initialize with public key -----
                    RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
                    RSAParameters RSAKeyInfo = new RSAParameters();
                    RSAKeyInfo.Modulus = modulus;
                    RSAKeyInfo.Exponent = exponent;
                    RSA.ImportParameters(RSAKeyInfo);

                    return RSA;
                }

            }
        }

        private bool CompareBytearrays(byte[] a, byte[] b)
        {
            if (a.Length != b.Length)
                return false;
            int i = 0;
            foreach (byte c in a)
            {
                if (c != b[i])
                    return false;
                i++;
            }
            return true;
        }
    }
}      

      雖然将公鑰暴露在js檔案中,但是如果需要解密得到明文,必須需要私鑰(這個存儲在背景,不容易擷取)。調試運作,可以看到擷取的密碼是加密後的資料,然後在背景可以進行解密擷取到明文。