Как я могу расшифровать данные кусками в С# с помощью закрытого ключа после шифрования в php с использованием открытого ключа?

Как расшифровать вывод этого кода с помощью закрытого ключа (формат pem) в C#?

$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# - дорога до сих пор


Ниже приведена моя попытка расшифровки в С#:

Использование :

// 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 байтов (даже если общее количество байтов меньше), а декодирование base64 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-скрипт для генерации открытого и закрытого ключей для тестирования на < strong>tehplayground.com.


person Kraang Prime    schedule 31.12.2016    source источник
comment
@MaartenBodewes - не просит писать код для меня. Но в любом случае, я вставляю его сейчас — к сожалению, нет «MCVE» для загрузки общедоступного сертификата PEM (что может быть задокументировано на этом сайте в сотнях мест), но я сделаю все возможное, чтобы разбить его на детские кусочки. --- обновление должно удовлетворить все ваши беды. Он был разбит на маленькие кусочки, однако решение по-прежнему не работает. Сбой расшифровки в C# может быть вызван буквально чем угодно - каждый фрагмент кода может привести к сбою, что приведет к rsa.Decrypt() barfing.   -  person Kraang Prime    schedule 31.12.2016
comment
Хорошо, так лучше. Чего все еще не хватает, так это описания того, как вышеприведенный код терпит неудачу. Обратите внимание, что шифрование с помощью закрытого ключа совершенно бессмысленно и, скорее всего, небезопасно, даже если открытый ключ хранится в секрете. Разделение открытого текста на куски тоже не очень хорошая идея, посмотрите, что такое гибридное шифрование.   -  person Maarten Bodewes    schedule 31.12.2016
comment
@MaartenBodewes - я понимаю это, но PGP нежизнеспособен, учитывая, что нет прямой реализации C #, а общедоступный и частный, с обеих сторон есть статьи от компаний, занимающихся безопасностью, в которых говорится, что шифровать с помощью pub, расшифровывать с помощью частного и наоборот. Кажется, ни у кого нет ни единого мнения о том, как это сделать, ни жизнеспособной открытой межъязыковой реализации. (C# ‹›php ‹›java ‹›ios) без использования сторонних библиотек, и даже до сих пор. Кроме того, я опубликовал обновление о том, где он терпит неудачу, но это не значит, что он не будет / не выйдет из строя где-либо еще. Мне нужна надежная реализация.   -  person Kraang Prime    schedule 31.12.2016
comment
@MaartenBodewes - ... кроме того, некоторое шифрование (даже если фрагментами из-за нарушенных ограничений) лучше, чем ничего. Альтернативой является написание метода шифрования с нуля или возврат к моему кросс-платформенному методу 3DES, который я написал для Objective C, Java, PHP, VB.NET и C#, — действительно хотелось бы уйти от этого.   -  person Kraang Prime    schedule 31.12.2016
comment
Любая охранная компания, описывающая шифрование с закрытым ключом, должна быть разобрана, уничтожена или уничтожена с орбиты, за возможным исключением того, что они имеют в виду генерацию подписи с использованием старых терминов PKCS#1 (OID для генерации подписи RSA по-прежнему шифрование RSA, что неверно, оно просто использует модульное возведение в степень, как и шифрование).   -  person Maarten Bodewes    schedule 31.12.2016
comment
Некоторое шифрование, такое как часто публикуемое здесь, на SO, хуже, чем отсутствие шифрования, потому что оно обеспечивает только ложное ощущение безопасности там, где его нет. PS дайте пожалуйста сообщение об ошибке!   -  person Maarten Bodewes    schedule 31.12.2016
comment
@MaartenBodewes - я чувствую то же самое в отношении 1/2 раздутых и / или сломанных методов / библиотек, находящихся в дикой природе. На самом деле это просто еще одна форма лжи, и дезинформация оказывает медвежью услугу шифрованию.   -  person Kraang Prime    schedule 31.12.2016
comment
Давайте продолжим обсуждение в чате.   -  person Maarten Bodewes    schedule 31.12.2016
comment
У меня сейчас работает расшифровка/шифрование, что было огромным препятствием (и обновил вопрос, используя правильный порядок шифрования/дешифрования - расшифровать с помощью частного - зашифровать с помощью общедоступного -. Все еще отсутствует то, на чем эта проблема была в первую очередь сосредоточена, на которой расшифровывается в блоках Размер блока зашифрованных данных больше, чем незашифрованная строка.   -  person Kraang Prime    schedule 31.12.2016
comment
Это нормально для заполнения RSA v1.5. Выходной зашифрованный текст имеет размер модуля (т.е. размер ключа), а входной на 11 байт меньше.   -  person Maarten Bodewes    schedule 31.12.2016
comment
Как я могу объяснить это, чтобы чтение фрагмента было надежным? Не будете ли вы так любезны продемонстрировать (поскольку это решит эту проблему). По сути, циклически перебирает декодированные байты base64 с шагом X, что соответствует 100-байтовым фрагментам php.   -  person Kraang Prime    schedule 31.12.2016
comment
Можно сначала декодировать base 64, потом разделить по модулю/размеру ключа, расшифровать и поместить в поток памяти. Затем, наконец, вы можете использовать этот поток для извлечения открытого текста (в виде байтов). Затем вам нужно преобразовать обратно в текст (если это был текст), и все готово.   -  person Maarten Bodewes    schedule 31.12.2016
comment
@MaartenBodewes - ясно, что у меня проблемы с размером - возьмите самый нижний пример кода для того, что я пробовал ранее. Однако нужно заменить 100 чем угодно и откуда угодно. rsa.KeySize это 4096.   -  person Kraang Prime    schedule 31.12.2016
comment
Ммм, я не вижу обсуждения в чате здесь. Можешь взглянуть?   -  person Maarten Bodewes    schedule 31.12.2016


Ответы (1)


После обширного разговора с автором вопроса кажется, что в коде есть две (основные) проблемы, которые помешали ему работать:

  1. Открытый ключ не был прочитан, так как код этого решения stackoverflow фактически создает не двоичный открытый ключ, а сертификат . Для этого можно использовать конструктор X509Certificate, за которым следует GetPublicKey. Метод в решении stackoverflow должен был называться по-другому. Позже он был заменен закрытым ключом (поскольку расшифровка с использованием открытого ключа не обеспечивает конфиденциальности).

  2. Считалось, что зашифрованные фрагменты имеют размер 100 байт, а размер ключа — 4096 бит (512 байт). Однако RSA (как указано в PKCS#1 v2.1 для заполнения PKCS#1 v1.5) всегда шифрует точно до размера ключа RSA (размер модуля) в байтах. Таким образом, входные данные для расшифровки также должны быть кусками по 512 байт. Однако вывод будет 100 байт, если он был зашифрован (в коде PHP).

Чтобы это работало, потребовалась небольшая модификация, чтобы зациклить декодированные в base64 байты зашифрованных данных в порциях, рассчитанных на основе KeySize / 8 (где 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 уязвимо для атак оракула заполнения, лучше убедитесь, что вы не разрешаете их, особенно в транспортных протоколах (или вместо этого используйте более новое заполнение OAEP);
  • доверяйте своему открытому ключу перед его использованием, иначе могут быть применены атаки «человек посередине».
person Maarten Bodewes    schedule 31.12.2016
comment
Следует отметить пару вещей: вопрос изменился, поэтому GetPublicKey не применяется со стороны С#. Кроме того, этот код stackoverflow ничего не создает :). Разделение на фрагменты выполняется по 100 байтов на фрагмент, однако для расшифровки, как указано, KeySize / 8 - это размер фрагмента для декодирования. Теперь, если бы только был способ использовать ключи pub/priv для шифрования больших данных без необходимости разделения. - person Kraang Prime; 31.12.2016
comment
Обычно вы шифруете ключ AES и используете его вместо этого. Это гибридная криптография, как указано в моем втором комментарии к вопросу (теперь первый). - person Maarten Bodewes; 31.12.2016
comment
Я добавил полученный исходный код, который сделал это для меня, к вашему решению. - person Kraang Prime; 31.12.2016