簡體   English   中英

使用公鑰在php中加密后,如何使用私鑰在c#中的塊中解密數據?

[英]How can I decrypt data in chunks in c# using a private key after encrypting in php using a public key?

如何使用C#中的私鑰(pem格式)解密此代碼的輸出?

$output = json_encode(array('see'=>'me'));

define('CIPHER_BLOCK_SIZE', 100);

$encrypted = '';
$key = file_get_contents('public.txt');

$chunks = str_split($output, CIPHER_BLOCK_SIZE);
foreach($chunks as $chunk)
{
  $chunkEncrypted = '';
  $valid = openssl_public_encrypt($chunk, $chunkEncrypted, $key, OPENSSL_PKCS1_PADDING);

  if($valid === false){
      $encrypted = '';
      break; //also you can return and error. If too big this will be false
  } else {
      $encrypted .= $chunkEncrypted;
  }
}
$output = base64_encode($encrypted); //encoding the whole binary String as MIME base 64

echo $output;

單擊此處以獲取可用的大型json示例,其格式可替換上述示例中的以下行以測試分塊,因為上述$output json太小而無法使分塊生效。

$output = json_encode(array('see'=>'me'));

上面代碼的作用說明

上面的代碼是此解決方案的修改,它將數據分成較小的塊(每個塊100個字節),並使用pem格式的公共密鑰對其進行加密。

目的

我正在研究對大於幾個字節的數據進行加密以更安全地傳輸數據,並且發現使用證書進行加密/解密是最好的選擇。

目的是加密php中的數據(使用私鑰),然后將數據以C#編寫的應用程序接收並解密(使用公鑰)。

C#-到目前為止的路


以下是我在c#中解密的嘗試:

用法:

// location of private certificate
string key = @"C:\path\to\private.txt";

// output from php script (encrypted)
string encrypted = "Bdm4s7aw.....Pvlzg=";

// decrypt and store decrypted string
string decrypted = crypt.decrypt( encrypted, key );

類:

public static string decrypt(string encrypted, string privateKey) {
    try {
        RSACryptoServiceProvider rsa = DecodePrivateKeyInfo( DecodePkcs8PrivateKey( File.ReadAllText( privateKey ) ) );
        return Encoding.UTF8.GetString( rsa.Decrypt( Convert.FromBase64String( encrypted ), false ) );
    } catch (CryptographicException ce) {
        return ce.Message;
    } catch (FormatException fe) {
        return fe.Message;
    } catch (IOException ie) {
        return ie.Message;
    } catch (Exception e) {
        return e.Message;
    }
}

這依賴於其他方法(從opensslkey.cs收獲)

//--------   Get the binary PKCS #8 PRIVATE key   --------
private static byte[] DecodePkcs8PrivateKey( string instr ) {
    const string pemp8header = "-----BEGIN PRIVATE KEY-----";
    const string pemp8footer = "-----END PRIVATE KEY-----";
    string pemstr = instr.Trim();
    byte[] binkey;
    if ( !pemstr.StartsWith( pemp8header ) || !pemstr.EndsWith( pemp8footer ) )
        return null;
    StringBuilder sb = new StringBuilder( pemstr );
    sb.Replace( pemp8header, "" );  //remove headers/footers, if present
    sb.Replace( pemp8footer, "" );

    string pubstr = sb.ToString().Trim();   //get string after removing leading/trailing whitespace

    try {
        binkey = Convert.FromBase64String( pubstr );
    } catch ( FormatException ) {        //if can't b64 decode, data is not valid
        return null;
    }
    return binkey;
}

//------- Parses binary asn.1 PKCS #8 PrivateKeyInfo; returns RSACryptoServiceProvider ---
private static RSACryptoServiceProvider DecodePrivateKeyInfo( byte[] pkcs8 ) {
    // encoded OID sequence for  PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
    // this byte[] includes the sequence byte and terminal encoded null
    byte[] SeqOID = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
    byte[] seq = new byte[15];
    // ---------  Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob  ------
    MemoryStream mem = new MemoryStream( pkcs8 );
    int lenstream = (int)mem.Length;
    BinaryReader binr = new BinaryReader( mem );    //wrap Memory Stream with BinaryReader for easy reading
    byte bt = 0;
    ushort twobytes = 0;

    try {

        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;


        bt = binr.ReadByte();
        if ( bt != 0x02 )
            return null;

        twobytes = binr.ReadUInt16();

        if ( twobytes != 0x0001 )
            return null;

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

        bt = binr.ReadByte();
        if ( bt != 0x04 )   //expect an Octet string
            return null;

        bt = binr.ReadByte();       //read next byte, or next 2 bytes is  0x81 or 0x82; otherwise bt is the byte count
        if ( bt == 0x81 )
            binr.ReadByte();
        else
            if ( bt == 0x82 )
            binr.ReadUInt16();
        //------ at this stage, the remaining sequence should be the RSA private key

        byte[] rsaprivkey = binr.ReadBytes( (int)( lenstream - mem.Position ) );
        RSACryptoServiceProvider rsacsp = DecodeRSAPrivateKey( rsaprivkey );
        return rsacsp;
    } catch ( Exception ) {
        return null;
    } finally { binr.Close(); }

}

//------- Parses binary ans.1 RSA private key; returns RSACryptoServiceProvider  ---
private static RSACryptoServiceProvider DecodeRSAPrivateKey( byte[] privkey ) {
    byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;

    // ---------  Set up stream to decode the asn.1 encoded RSA private key  ------
    MemoryStream mem = new MemoryStream( privkey );
    BinaryReader binr = new BinaryReader( mem );    //wrap Memory Stream with BinaryReader for easy reading
    byte bt = 0;
    ushort twobytes = 0;
    int elems = 0;
    try {
        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();
        if ( twobytes != 0x0102 )   //version number
            return null;
        bt = binr.ReadByte();
        if ( bt != 0x00 )
            return null;


        //------  all private key components are Integer sequences ----
        elems = GetIntegerSize( binr );
        MODULUS = binr.ReadBytes( elems );

        elems = GetIntegerSize( binr );
        E = binr.ReadBytes( elems );

        elems = GetIntegerSize( binr );
        D = binr.ReadBytes( elems );

        elems = GetIntegerSize( binr );
        P = binr.ReadBytes( elems );

        elems = GetIntegerSize( binr );
        Q = binr.ReadBytes( elems );

        elems = GetIntegerSize( binr );
        DP = binr.ReadBytes( elems );

        elems = GetIntegerSize( binr );
        DQ = binr.ReadBytes( elems );

        elems = GetIntegerSize( binr );
        IQ = binr.ReadBytes( elems );

        // ------- create RSACryptoServiceProvider instance and initialize with public key -----
        RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
        RSAParameters RSAparams = new RSAParameters();
        RSAparams.Modulus = MODULUS;
        RSAparams.Exponent = E;
        RSAparams.D = D;
        RSAparams.P = P;
        RSAparams.Q = Q;
        RSAparams.DP = DP;
        RSAparams.DQ = DQ;
        RSAparams.InverseQ = IQ;
        RSA.ImportParameters( RSAparams );
        return RSA;
    } catch ( Exception ) {
        return null;
    } finally { binr.Close(); }
}

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

    if ( bt == 0x81 )
        count = binr.ReadByte();    // data size in next byte
    else
    if ( bt == 0x82 ) {
        highbyte = binr.ReadByte(); // data size in next 2 bytes
        lowbyte = binr.ReadByte();
        byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
        count = BitConverter.ToInt32( modint, 0 );
    } else {
        count = bt;     // we already have the data size
    }



    while ( binr.ReadByte() == 0x00 ) { //remove high order zeros in data
        count -= 1;
    }
    binr.BaseStream.Seek( -1, SeekOrigin.Current );     //last ReadByte wasn't a removed zero, so back up a byte
    return count;
}

private static 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;
}

現在所有功能都可以使用,但是在解密過程中仍未合並分塊。

我必須做些什么才能讀入這些塊,因為更大的文件肯定會大於原始的未加密數據。

我以前的嘗試是嘗試類似以下代碼的內容,但這似乎有缺陷,因為它總是填充100個字節(即使總字節數更少),也可以對json_encode(array('see'=>'me'))使用我當前的公共密鑰進行加密最終為512字節。

    byte[] buffer = new byte[100]; // the number of bytes to decrypt at a time
    int bytesReadTotal = 0;
    int bytesRead = 0;
    string decrypted = "";
    byte[] decryptedBytes;
    using ( Stream stream = new MemoryStream( data ) ) {
        while ( ( bytesRead = await stream.ReadAsync( buffer, bytesReadTotal, 100 ) ) > 0 ) {
            decryptedBytes = rsa.Decrypt( buffer, false );
            bytesReadTotal = bytesReadTotal + bytesRead;
            decrypted = decrypted + Encoding.UTF8.GetString( decryptedBytes );
        }
    }

    return decrypted;

為了方便起見,我放置了一個php腳本來生成一個公鑰和私鑰,以便tehplayground.com進行測試

與問題的作者進行廣泛交談之后,似乎在代碼中有兩個(主要)問題使該問題無法正常工作:

  1. 未讀取公共密鑰,因為此stackoverflow解決方案中代碼實際上不是創建二進制公共密鑰而是證書。 為此,可以使用X509Certificate構造函數,然后使用GetPublicKey stackoverflow解決方案中的方法應該以不同的方式命名。 后來將其更改為私鑰(因為使用公鑰進行的解密不提供機密性)。

  2. 加密塊的大小被認為是100個字節,而密鑰大小是4096位(512字節)。 但是,RSA(如PKCS#1 v1.5中針對PKCS#1 v1.5填充所指定的) 始終會加密為以字節為單位的RSA密鑰大小(模數大小)。 因此,解密的輸入也應該是512字節的塊。 但是,如果將其加密(在PHP代碼中),則輸出為100字節。

為了使這項工作有效,有必要進行少量修改以循環遍歷基於KeySize / 8計算的塊中的加密數據的base64解碼字節(其中8是一個字節中的多少位,因為KeySize是一個int值,表示每個字節中有多少字節)塊是)。

public static async Task<string> decrypt(string encrypted, string privateKey) {
        // read private certificate into RSACryptoServiceProvider from file
        RSACryptoServiceProvider rsa = DecodePrivateKeyInfo( DecodePkcs8PrivateKey( File.ReadAllText( privateKey ) ) );

        // decode base64 to bytes
        byte[] encryptedBytes = Convert.FromBase64String( encrypted );
        int bufferSize = (int)(rsa.KeySize / 8);

        // initialize byte buffer based on certificate block size
        byte[] buffer = new byte[bufferSize]; // the number of bytes to decrypt at a time
        int bytesReadTotal = 0;    int bytesRead = 0;
        string decrypted = "";     byte[] decryptedBytes;

        // convert byte array to stream
        using ( Stream stream = new MemoryStream( encryptedBytes ) ) {

            // loop through stream for each block of 'bufferSize'
            while ( ( bytesRead = await stream.ReadAsync( buffer, bytesReadTotal, bufferSize ) ) > 0 ) {

                // decrypt this chunk
                decryptedBytes = rsa.Decrypt( buffer, false );

                // account for bytes read & decrypted
                bytesReadTotal = bytesReadTotal + bytesRead;

                // append decrypted data as string for return
                decrypted = decrypted + Encoding.UTF8.GetString( decryptedBytes );
            }
        }

        return decrypted;
}

安全說明:

  • PKCS#1 v1.5填充很容易受到oracle填充攻擊,最好確保您不允許這樣做,尤其是在傳輸協議中(或者使用更新的OAEP填充);
  • 在使用前請先信任您的公鑰,否則可能會受到中間人攻擊。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM