offtopic: the name did not come up with better

There is a decompiled flash application, part of the logic of which I want to use in C #.

To begin with, I will give the cleaned part of the application with the logic of interest:

package some_package { import flash.net.URLLoader; import flash.net.URLRequest; import flash.utils.ByteArray; import com.hurlant.util.Hex; import com.hurlant.crypto.symmetric.NullPad; import com.hurlant.crypto.symmetric.IPad; import com.hurlant.crypto.Crypto; import com.hurlant.crypto.symmetric.ICipher; import flash.events.Event; import com.hurlant.crypto.rsa.RSAKey; import com.hurlant.util.der.PEM; import flash.display.LoaderInfo; import flash.net.URLRequestMethod; import flash.net.URLLoaderDataFormat; public class Requester { private const P_KEY:String = "-----BEGIN RSA PRIVATE KEY-----" + "private key line" + "private key line" + "private key line" + "private key line" + "private key line" + "private key line" + "private key line" + "private key line" + "private key line" + "private key line" + "private key line" + "private key line" + "private key line" + "-----END RSA PRIVATE KEY-----"; private var _loader:URLLoader; var urlReq:URLRequest; public function Requester( requestUrl:String ) { super(); this.urlReq = new URLRequest( requestUrl ); this.urlReq.method = URLRequestMethod.GET; this._loader = new URLLoader( this.urlReq ); this._loader.addEventListener( Event.COMPLETE, this.onReqComplete ); this._loader.dataFormat = URLLoaderDataFormat.BINARY; this._loader.load( this.urlReq ); } private function getEncKey(from:ByteArray) : ByteArray { var keySize:uint = 128; var toReturn:ByteArray = new ByteArray(); from.readBytes( toReturn, 0, keySize ); return toReturn; } private function decryptString( toDecrypt:String, key:String ) : String { var algorithm:String = "simple-blowfish-cbc8"; var keyReady:* = Hex.toArray( Hex.fromString( key ) ); var toDecryptReady:* = Hex.toArray( toDecrypt ); var padder:IPad = new NullPad(); var cipher:ICipher = Crypto.getCipher( algorithm, keyReady, padder ); padder.setBlockSize(cipher.getBlockSize()); cipher.decrypt( toDecryptReady ); return Hex.toString( Hex.fromArray( toDecryptReady ) ); } public function onReqComplete( ev:Event ) : * { var sMessage:ByteArray = null; var len:uint = 0; var sign:ByteArray = null; var priv_key:RSAKey = null; var dst:ByteArray = null; var pwd:String = null; var offset:uint = 0; var conf_data:String = null; var dec:String = null; var resultObj:Object = null; var event:Event = ev; sMessage = ByteArray( event.target.data as ByteArray ); len = sMessage.length; sign = this.getEncKey( sMessage ); priv_key = PEM.readRSAPrivateKey( this.P_KEY ); dst = new ByteArray(); priv_key.decrypt( sign, dst, sign.length ); pwd = Hex.toString( Hex.fromArray( dst ) ); offset = String( sign ).length; conf_data = String( sMessage ).substr( offset, len - offset ); dec = this.decryptString( conf_data, pwd ); resultObj = dec; //do smth with decrypted string this._loader.close(); return; } } } 

Now I will describe how I understand it - the problem may well be in semantics.

In the constructor, we initialize the HTTP client with the onReqComplete success onReqComplete and send a GET request to the URL specified by the parameter.

onReqComplete gets the response as an array of bytes (I think, without headers). After that, it takes the first 128 bytes of the response getEncKey function and decrypts them with the RSA private key from the P_KEY constant. Used by pkcs1 padding, sources .

Then something strange begins to happen. I'm not sure that this is something meaningful; Perhaps this is nonsense, generated by the decompiler or application developer.

The just decrypted part of the message is converted to a string (only to be converted back into the decryptString function), which later will be used as the key in decryptString .

The rest of the message is also converted to a string. But a substring is taken from it, starting with the length of the original unencrypted first 128 bytes, converted to a string . This substring will be converted into an array of bytes and will be decrypted in decryptString .

All these transformations seem to me very strange. Among other things, I can't reproduce the AS String (smth: ByteArray) function in C #. The string received by AS String () from the first 128 bytes of the response is 122 characters long and is quite similar to strings obtained using C # Encoding.UTF8.GetString or StreamReader , but the first option gives a string of 128 characters and the second from 118 This seems to me very strange, I suspect that there may be some strange data loss.

Since I do not understand these conversions, I simply take all the bytes after 128 for decryption and this may be my mistake .

Next we go to the decryptString function.

It converts the key and data back into byte arrays. After that, she tries to decrypt the data using the blowfish algorithm in CBC IV mode. She also declares the algorithm as simple ( lib explanation for "simple" ) and that he should use the NullPad padder ( which, in fact, does nothing ).

In general, that's all; I think - nothing complicated.

I got this C # code:

 using System; using System.IO; using System.Text; using System.Threading.Tasks; using System.Collections.Generic; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Engines; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Crypto.Encodings; using Org.BouncyCastle.OpenSsl; using Org.BouncyCastle.Crypto.Modes; namespace SomeNamespace { internal sealed class SomeClass { private const string _privateRSAKeyString = "private key in PEM format with lines separated by \\n symbol"; private const int _blowfishBlockSize = 8; public async Task<string> AnalyzeAsync() { byte[] response; using ( var ms = new MemoryStream() ) {//response is cached for testing purposes using ( var fw = new StreamReader( "response.txt", Encoding.UTF8 ) ) ( await fw.ReadLineAsync().ConfigureAwait( false ) ) .Split( new[] { ',' }, StringSplitOptions.RemoveEmptyEntries ) .ForEach( b => ms.WriteByte( Byte.Parse( b ) ) ); ms.Position = 0; response = await ms.ReadToNewByteArrayAsync( (int) ms.Length ).ConfigureAwait( false ); } byte[] blowfishSignRSAEncrypted, IV, blowfishEncryptedData; using ( MemoryStream ms = new MemoryStream( response, false ) ) { blowfishSignRSAEncrypted = await ms.ReadToNewByteArrayAsync( 128 ).ConfigureAwait( false ); IV = await ms.ReadToNewByteArrayAsync( _blowfishBlockSize ).ConfigureAwait( false ); ms.Position = 128; blowfishEncryptedData = await ms.ReadToNewByteArrayAsync( (int) ( ms.Length - ms.Position ) ).ConfigureAwait( false ); } var rsaEngine = new Pkcs1Encoding( new RsaEngine() ); using ( var txtreader = new StringReader( _privateRSAKeyString ) ) { var keyPair = (AsymmetricCipherKeyPair) new PemReader( txtreader ).ReadObject(); rsaEngine.Init( false, keyPair.Private ); } byte[] blowfishDecryptedSign = rsaEngine.ProcessBlock( blowfishSignRSAEncrypted, 0, blowfishSignRSAEncrypted.Length ); var blowfishEngine = new BlowfishEngine(); var cipher = new BufferedBlockCipher( new CbcBlockCipher( new BlowfishEngine() ) ); cipher.Init( false, new ParametersWithIV( new KeyParameter( blowfishDecryptedSign ), IV ) ); var dataDecrypted = cipher.ProcessBytes( blowfishEncryptedData ); return Encoding.UTF8.GetString( dataDecrypted ); } } public static async Task<byte[]> ReadToNewByteArrayAsync( this MemoryStream ms, int length ) { byte[] whatIsRightfullyYours = new byte[ length ]; int offset = 0; //Stream.Read* return value is designed to be the way to ensure that you got exactly that much data as you need. IDK if that's true for MemoryStream, so let's check it. while ( ( offset += await ms.ReadAsync( whatIsRightfullyYours, offset, length - offset ).ConfigureAwait( false ) ) < length ) ; return whatIsRightfullyYours; } } 

The problem is that this code returns a meaningless sequence of characters and I do not understand what's the matter.

I would be grateful for any

UPD.1

In the process of research, I guessed to do the banal test "encrypt-decipher".

Decryption (the one that is already presented a little higher) failed to restore the original string, so the problem is precisely in it (and not in some mythical difference between the work of C # and AS or the dark magic of string conversion).

UPD.2

The previous update is a lie. Both the encryption and the decryption were wrong. In decryption, the ciphertext included IV ( ms.Position = 128; ). In encryption, however, the option was not taken into account when the message size was not a multiple of the size of the cipher block, because of which the source line was cut off by the library, which led to an incorrect restoration of the source line.

    1 answer 1

    The problem was that I didn’t pay enough attention to the var keyReady:* = Hex.toArray( Hex.fromString( key ) ); var toDecryptReady:* = Hex.toArray( toDecrypt ); fragment var keyReady:* = Hex.toArray( Hex.fromString( key ) ); var toDecryptReady:* = Hex.toArray( toDecrypt ); var keyReady:* = Hex.toArray( Hex.fromString( key ) ); var toDecryptReady:* = Hex.toArray( toDecrypt );

    Namely, the fact that a ciphertext before converting from HEX to a byte array is not converted to HEX from a UTF8 string. Accordingly, the problem was that after (correct) decryption of the Blowfish-key, I tried to decipher the wrong message (encoded in Hex) with it.

    A sufficient solution is to call Org.BouncyCastle.Utilities.Encoders.Hex.Decode(ciphertextWithIV); before splitting the message into IV and message.

    • I do not know how this question is useful for stackoverflow, I will leave it here just in case. Sorry about his removal will not. - bessgeor