Archived Forum Post

Index of archived forum posts

Question:

Encryption conundrum

Oct 24 '13 at 08:29

This is a bit of a long shot but .....

I have an ancient 3 party dll that I am trying to replace with your activex components

It contains various hashing and encryption routines but I have no idea on their internal workings as I have no documentation.

I've successfully got an md5 hash working but am struggling with two functionms called DESEncryptString DESDecryptString which does not tell me much

If I call DESEncryptString three times with the following string "The quick brown fox jumps over the lazy dog 0123456789" and key "big secret" then I get the following results

GKHdNpSta9X0gFzw4JAb4LEUIE6/wXNUpjiuNv+eaULfCmoL//Rozc/mIMYe/FG2Xqenm4jUQ2N6An2Kd85ooAzXTZhAggUS8x3eYs+TLdiHQA3w2yMUAHHSnKbwfvWn

On2muvrS0egDvFGbedPs4aHvxwO+nI4R15WIU0hy5mP1NZW8UkiZ38g00cCICL5OgGYzDCt1TVUSObJGJBcrYZmF+Fn/fH4W1cWmm7RONfReXjIrSd6tbdVwjV8qZHF9

ifcoWWx4weziL6gGmDFoBoJpHZb0xLlFeTaZSrzvFwf6ERd4ZyBX5KZN/6Wo8ztZT1JWv/vY8oqSLuvk4ykQZVx3JDypqGh59C+/xKI0ROM0H4IFg7amcGFJQDnsOFYI

As you can see they are very different but all will convert back into "The quick brown ..." using the decrypt

I've had a few goes with EncryptStringENC with different parameters but cant anything like this length of string back

They are all 128 characters long which might mean something ?

I'm no Alan Turing but can you tell anything about the above to be able to reproduce it ?

Thanks


Answer

Symmetric encryption algorithms such as AES, Blowfish, DES, RC2, etc. use binary keys of a particular length. The length of the key generally indicates the strength of the encryption. Therefore, if using 128-bit AES encryption, the key will be exactly 16 bytes. If you have a software API that uses an arbitrary length password string, then it must be that the string is transformed into a binary key of the length required. The 1st task is to understand the exact password string to binary secret key transformation algorithm that is used. Once you are assured of using the exact same key, you can continue to ensure that other encryption params are identical (cipher mode, IV, padding, etc.)


Answer

Thanks very much for the reply I have downloaded PE Explorer and got it to disassemble the dll i'm trying to replace I know it's clutching at straws but i've attached a screen grab of a secion of the strings window As you will see it shows DES-EDE2 which looks like it's something being passed somewhere. Looking at your sample code I can't see EDE anywhere and looking on the web seems to suggest it's some form of triple DES 128-bit ??? It maybe a complete red herring but does this help ?? Thanks Disassemble


Answer

OK I seem to have found some C source code for the old function in our version control system and i've pasted in sections below.

The code uses the library http://www.cryptopp.com/

When we make the call to DFX_EncryptString (our function name) we pass "base64" along with the data and passphrase

I can see as part of the DefaultEncryptorWithMAC from the library it talks of a key length of 16 and uses a Mash function that does some complex stuff that in their words

// The purpose of this function Mash() is to take an arbitrary length input
// string and *deterministicly* produce an arbitrary length output string such
// that (1) it looks random, (2) no information about the input is
// deducible from it, and (3) it contains as much entropy as it can hold, or
// the amount of entropy in the input string, whichever is smaller.

Here is the function we export from the dll

Hopefully there is enough here to work out what we need to do to replicate it

13.04.2005: EN : New string encryption routine:

int DFXAPI DFX_EncryptString(const char * encoding, const char * instring, char * outstring, int outbuffsize, const char * passphrase)
{
    // Encrypts the given string buffer using the given passphrase and returns the output in the
    // buffer provided. Make sure that the size of the passed buffer is adequate.
    string ls;

    BufferedTransformation * encryptor = NULL;

    try {

        if (!strcmp(encoding, "base64")) encryptor = new DefaultEncryptorWithMAC(passphrase, new Base64Encoder(new StringSink(ls), false));
        if (!strcmp(encoding, "hex")) encryptor = new DefaultEncryptorWithMAC(passphrase, new HexEncoder(new StringSink(ls)));
        if (!strcmp(encoding, "zlib")) encryptor = new DefaultEncryptorWithMAC (passphrase, new ZlibCompressor(new StringSink(ls)));
        if (!strcmp(encoding, "none")) encryptor = new DefaultEncryptorWithMAC (passphrase, new StringSink(ls));

        encryptor->Put((byte *)instring, strlen(instring));
        encryptor->MessageEnd();
    }
    catch (...) {
        // Catch all errors
        ls = "ERROR";
    }

    // Cleanup:
    if (encryptor!=NULL) delete encryptor;

    // Copy returned string into the output buffer:
    strncpy(outstring, ls.c_str(), (size_t)outbuffsize);

    return ls.size();
}

And here is the default.cpp file from the library that contains DefaultEncryptorWithMAC and the Mash function etc

// default.cpp - written and placed in the public domain by Wei Dai

#include "pch.h"
#include "default.h"
#include "queue.h"
#include <time.h>
#include <memory>

NAMESPACE_BEGIN(CryptoPP)

static const unsigned int MASH_ITERATIONS = 200;
static const unsigned int SALTLENGTH = 8;
static const unsigned int BLOCKSIZE = Default_BlockCipher::Encryption::BLOCKSIZE;
static const unsigned int KEYLENGTH = Default_BlockCipher::Encryption::DEFAULT_KEYLENGTH;

// The purpose of this function Mash() is to take an arbitrary length input
// string and *deterministicly* produce an arbitrary length output string such
// that (1) it looks random, (2) no information about the input is
// deducible from it, and (3) it contains as much entropy as it can hold, or
// the amount of entropy in the input string, whichever is smaller.

static void Mash(const byte *in, word16 inLen, byte *out, word16 outLen, int iterations)
{
    unsigned int bufSize = (outLen-1+DefaultHashModule::DIGESTSIZE-((outLen-1)%DefaultHashModule::DIGESTSIZE));

    // ASSERT: bufSize == (the smallest multiple of DIGESTSIZE that is >= outLen)

    byte b[2];
    SecByteBlock buf(bufSize);
    SecByteBlock outBuf(bufSize);
    DefaultHashModule hash;

    unsigned int i;
    for(i=0; i<outLen; i+=DefaultHashModule::DIGESTSIZE)
    {
        b[0] = (byte) i >> 8;
        b[1] = (byte) i;
        hash.Update(b, 2);
        hash.Update(in, inLen);
        hash.Final(outBuf+i);
    }

    while (iterations-- > 1)
    {
        memcpy(buf, outBuf, bufSize);
        for (i=0; i<bufSize; i+=DefaultHashModule::DIGESTSIZE)
        {
            b[0] = (byte) i >> 8;
            b[1] = (byte) i;
            hash.Update(b, 2);
            hash.Update(buf, bufSize);
            hash.Final(outBuf+i);
        }
    }

    memcpy(out, outBuf, outLen);
}

static void GenerateKeyIV(const byte *passphrase, unsigned int passphraseLength, const byte *salt, unsigned int saltLength, byte *key, byte *IV)
{
    SecByteBlock temp(passphraseLength+saltLength);
    memcpy(temp, passphrase, passphraseLength);
    memcpy(temp+passphraseLength, salt, saltLength);
    SecByteBlock keyIV(KEYLENGTH+BLOCKSIZE);
    Mash(temp, passphraseLength + saltLength, keyIV, KEYLENGTH+BLOCKSIZE, MASH_ITERATIONS);
    memcpy(key, keyIV, KEYLENGTH);
    memcpy(IV, keyIV+KEYLENGTH, BLOCKSIZE);
}

// ********************************************************

DefaultEncryptor::DefaultEncryptor(const char *passphrase, BufferedTransformation *attachment)
    : ProxyFilter(NULL, 0, 0, attachment), m_passphrase((const byte *)passphrase, strlen(passphrase))
{
}

DefaultEncryptor::DefaultEncryptor(const byte *passphrase, unsigned int passphraseLength, BufferedTransformation *attachment)
    : ProxyFilter(NULL, 0, 0, attachment), m_passphrase(passphrase, passphraseLength)
{
}

void DefaultEncryptor::FirstPut(const byte *)
{
    // VC60 workaround: __LINE__ expansion bug
    CRYPTOPP_COMPILE_ASSERT_INSTANCE(SALTLENGTH <= DefaultHashModule::DIGESTSIZE, 1);
    CRYPTOPP_COMPILE_ASSERT_INSTANCE(BLOCKSIZE <= DefaultHashModule::DIGESTSIZE, 2);

    SecByteBlock salt(DefaultHashModule::DIGESTSIZE), keyCheck(DefaultHashModule::DIGESTSIZE);
    DefaultHashModule hash;

    // use hash(passphrase | time | clock) as salt
    hash.Update(m_passphrase, m_passphrase.size());
    time_t t=time(0);
    hash.Update((byte *)&t, sizeof(t));
    clock_t c=clock();
    hash.Update((byte *)&c, sizeof(c));
    hash.Final(salt);

    // use hash(passphrase | salt) as key check
    hash.Update(m_passphrase, m_passphrase.size());
    hash.Update(salt, SALTLENGTH);
    hash.Final(keyCheck);

    AttachedTransformation()->Put(salt, SALTLENGTH);

    // mash passphrase and salt together into key and IV
    SecByteBlock key(KEYLENGTH);
    SecByteBlock IV(BLOCKSIZE);
    GenerateKeyIV(m_passphrase, m_passphrase.size(), salt, SALTLENGTH, key, IV);

    m_cipher.SetKeyWithIV(key, key.size(), IV);
    SetFilter(new StreamTransformationFilter(m_cipher));

    m_filter->Put(keyCheck, BLOCKSIZE);
}

void DefaultEncryptor::LastPut(const byte *inString, unsigned int length)
{
    m_filter->MessageEnd();
}

// ********************************************************

DefaultDecryptor::DefaultDecryptor(const char *p, BufferedTransformation *attachment, bool throwException)
    : ProxyFilter(NULL, SALTLENGTH+BLOCKSIZE, 0, attachment)
    , m_state(WAITING_FOR_KEYCHECK)
    , m_passphrase((const byte *)p, strlen(p))
    , m_throwException(throwException)
{
}

DefaultDecryptor::DefaultDecryptor(const byte *passphrase, unsigned int passphraseLength, BufferedTransformation *attachment, bool throwException)
    : ProxyFilter(NULL, SALTLENGTH+BLOCKSIZE, 0, attachment)
    , m_state(WAITING_FOR_KEYCHECK)
    , m_passphrase(passphrase, passphraseLength)
    , m_throwException(throwException)
{
}

void DefaultDecryptor::FirstPut(const byte *inString)
{
    CheckKey(inString, inString+SALTLENGTH);
}

void DefaultDecryptor::LastPut(const byte *inString, unsigned int length)
{
    if (m_filter.get() == NULL)
    {
        m_state = KEY_BAD;
        if (m_throwException)
            throw KeyBadErr();
    }
    else
    {
        m_filter->MessageEnd();
        m_state = WAITING_FOR_KEYCHECK;
    }
}

void DefaultDecryptor::CheckKey(const byte *salt, const byte *keyCheck)
{
    SecByteBlock check(STDMAX((unsigned int)2*BLOCKSIZE, (unsigned int)DefaultHashModule::DIGESTSIZE));

    DefaultHashModule hash;
    hash.Update(m_passphrase, m_passphrase.size());
    hash.Update(salt, SALTLENGTH);
    hash.Final(check);

    SecByteBlock key(KEYLENGTH);
    SecByteBlock IV(BLOCKSIZE);
    GenerateKeyIV(m_passphrase, m_passphrase.size(), salt, SALTLENGTH, key, IV);

    m_cipher.SetKeyWithIV(key, key.size(), IV);
    std::auto_ptr<StreamTransformationFilter> decryptor(new StreamTransformationFilter(m_cipher));

    decryptor->Put(keyCheck, BLOCKSIZE);
    decryptor->ForceNextPut();
    decryptor->Get(check+BLOCKSIZE, BLOCKSIZE);

    SetFilter(decryptor.release());

    if (memcmp(check, check+BLOCKSIZE, BLOCKSIZE))
    {
        m_state = KEY_BAD;
        if (m_throwException)
            throw KeyBadErr();
    }
    else
        m_state = KEY_GOOD;
}

// ********************************************************

static DefaultMAC * NewDefaultEncryptorMAC(const byte *passphrase, unsigned int passphraseLength)
{
    unsigned int macKeyLength = DefaultMAC::StaticGetValidKeyLength(16);
    SecByteBlock macKey(macKeyLength);
    // since the MAC is encrypted there is no reason to mash the passphrase for many iterations
    Mash(passphrase, passphraseLength, macKey, macKeyLength, 1);
    return new DefaultMAC(macKey, macKeyLength);
}

DefaultEncryptorWithMAC::DefaultEncryptorWithMAC(const char *passphrase, BufferedTransformation *attachment)
    : ProxyFilter(NULL, 0, 0, attachment)
    , m_mac(NewDefaultEncryptorMAC((const byte *)passphrase, strlen(passphrase)))
{
    SetFilter(new HashFilter(*m_mac, new DefaultEncryptor(passphrase), true));
}

DefaultEncryptorWithMAC::DefaultEncryptorWithMAC(const byte *passphrase, unsigned int passphraseLength, BufferedTransformation *attachment)
    : ProxyFilter(NULL, 0, 0, attachment)
    , m_mac(NewDefaultEncryptorMAC(passphrase, passphraseLength))
{
    SetFilter(new HashFilter(*m_mac, new DefaultEncryptor(passphrase, passphraseLength), true));
}

void DefaultEncryptorWithMAC::LastPut(const byte *inString, unsigned int length)
{
    m_filter->MessageEnd();
}

// ********************************************************

DefaultDecryptorWithMAC::DefaultDecryptorWithMAC(const char *passphrase, BufferedTransformation *attachment, bool throwException)
    : ProxyFilter(NULL, 0, 0, attachment)
    , m_mac(NewDefaultEncryptorMAC((const byte *)passphrase, strlen(passphrase)))
    , m_throwException(throwException)
{
    SetFilter(new DefaultDecryptor(passphrase, m_hashVerifier=new HashVerifier(*m_mac, NULL, HashVerifier::PUT_MESSAGE), throwException));
}

DefaultDecryptorWithMAC::DefaultDecryptorWithMAC(const byte *passphrase, unsigned int passphraseLength, BufferedTransformation *attachment, bool throwException)
    : ProxyFilter(NULL, 0, 0, attachment)
    , m_mac(NewDefaultEncryptorMAC(passphrase, passphraseLength))
    , m_throwException(throwException)
{
    SetFilter(new DefaultDecryptor(passphrase, passphraseLength, m_hashVerifier=new HashVerifier(*m_mac, NULL, HashVerifier::PUT_MESSAGE), throwException));
}

DefaultDecryptor::State DefaultDecryptorWithMAC::CurrentState() const
{
    return static_cast<const DefaultDecryptor *>(m_filter.get())->CurrentState();
}

bool DefaultDecryptorWithMAC::CheckLastMAC() const
{
    return m_hashVerifier->GetLastResult();
}

void DefaultDecryptorWithMAC::LastPut(const byte *inString, unsigned int length)
{
    m_filter->MessageEnd();
    if (m_throwException && !CheckLastMAC())
        throw MACBadErr();
}

NAMESPACE_END

Answer

In the hope that this more succinct descripton towards the bottom of this code project page might help move things forward http://www.codeproject.com/Articles/21877/Applied-Crypto-Block-Ciphers

The text reads Encryptors with MACs Using a symmetric cipher with a MAC allows us to provide both confidentiality and integrity. Crypto++ provides us with DefaultEncryptorWithMAC and DefaultDecryptorWithMAC in default.h. From the typedefs provided in default.h, the Default[En/De]cryptorWithMAC class uses triple DES (class DES_EDE2) as the block cipher in CBC mode, and SHA as the hash. The class is straightforward to use (provided in Sample 11):

string message = "secret message";    
string password = "password";
string encrypted, recovered;

StringSource(
message,
true,
new DefaultEncryptorWithMAC(
    password.c_str(),
    new StringSink( encrypted )
) // DefaultEncryptorWithMAC
); // StringSource

StringSource(
encrypted,
true,
new DefaultDecryptorWithMAC(
    password.c_str(),
    new StringSink( recovered )
) // DefaultDecryptorWithMAC
); // StringSource

cout << "Recovered Text:" << endl;
cout << "  " << recovered << endl;

The only difference with the one i'm trying to emulate is it Base64 the result

encryptor = new DefaultEncryptorWithMAC(passphrase, new Base64Encoder(new StringSink(ls), false));
decryptor = new Base64Decoder(new DefaultDecryptorWithMAC(passphrase, new StringSink(ls)));

Thanks in advance as always for any help


Answer

In the hope I can help move this along i've done some more investigation and have now just tried the "sledgehammer" approach.

Talking an encrypted string like this

guH4wN7mDDmikVqFkaDt4VgwOK6J5LfOm+juHnOivCGPKYOZ9d0yoYktNulm0vD8V6lGfBvfNqrwXSQ7YKrDsy8sdgKYNQri

I've tried the following 40 combinations of

Get Create U_cComChilkatCrypt2 to hoCrypt
Get ComUnlockComponent  of hoCrypt UNLOCK_CHILKAT_Crypt to bOK
Set ComCryptAlgorithm   of hoCrypt to "des"     // des and 3des
Set ComCipherMode       of hoCrypt to "cbc"     // always cbc
Set ComKeyLength        of hoCrypt to 192       // 192 and 168
Set ComPaddingScheme    of hoCrypt to 4         // 0,1,2,3,4
Set ComEncodingMode     of hoCrypt to "none"    // always "none" as above string looks unencoded
Set ComCharset          of hoCrypt to "utf-8"   // no >128 in orig but just incase 
Move (Uppercase(newMD5("Not Orig but is corect length"))) to sKey  //MD5 or SHA this using your hash functions
Send ComSetEncodedKey   of hoCrypt sKey "hex"
Get ComDecryptStringENC of hoCrypt sVal to sDecrypted
Send Destroy of hoCrypt

Having tried all 40 without luck is there any others I should be trying ?

I have managed to establish that the crypto++ libaray uses the seconds since 1/1/1970 and a cpu tickcount as part of the EncodedIV although as this is random I assume the absolute value is not critical to decryption as it can be deduced ?

Any assistance with this would be greatly appreciated

Thanks


Answer

OK futher to above i've modifed it to take the various properties so I can loop throu 11,000 or so combinations but stiull havent found the magic one

I'm starting with an encrypted string and a 29 character key in terms of what the user wants ass a 'password' That is all that is being provided to the Crypto++ decrypt function to get the right answer back but so far have not found a combinatoin that will decrypt it using your compontents

I know what i'm doing is not very scientific but there cant be that mant combinations of properties ?

I'd really like to get this knocked on the head

Thanks

Move "ANSI" to sCharSets[SizeOfArray(sCharSets)]
Move "us-ascii" to sCharSets[SizeOfArray(sCharSets)]
...

Move "cbc" to sCypherMode[SizeOfArray(sCypherMode)]
Move "ecb" to sCypherMode[SizeOfArray(sCypherMode)]
Move "cfb" to sCypherMode[SizeOfArray(sCypherMode)]

Direct_Output "directyouttesty.txt"

For iLoop3 from 0 to (SizeOfArray(sCharSets)-1)
    For iLoop1 from 0 to 4
        For iLoop2 from 0 to 1
            For iLoop4 from 0 to (SizeOfArray(sCypherMode)-1)
                Move (DESDecrypt2(s1,"des",sCypherMode[iLoop4],168,iLoop1,sCharSets[iLoop3],iLoop2)) to s3
                Writeln "des " sCypherMode[iLoop4] " 168 " (String(iLoop1)) " " sCharSets[iLoop3] " " iLoop2 " " s3
                Move (DESDecrypt2(s1,"des",sCypherMode[iLoop4],192,iLoop1,sCharSets[iLoop3],iLoop2)) to s3
                Writeln "des " sCypherMode[iLoop4] " 192 " (String(iLoop1)) " " sCharSets[iLoop3] " " iLoop2 " " s3
                Move (DESDecrypt2(s1,"3des",sCypherMode[iLoop4],168,iLoop1,sCharSets[iLoop3],iLoop2)) to s3
                Writeln "3des " sCypherMode[iLoop4] " 168 " (String(iLoop1)) " " sCharSets[iLoop3] " " iLoop2 " " s3
                Move (DESDecrypt2(s1,"3des",sCypherMode[iLoop4],192,iLoop1,sCharSets[iLoop3],iLoop2)) to s3
                Writeln "3des " sCypherMode[iLoop4] " 192 " (String(iLoop1)) " " sCharSets[iLoop3] " " iLoop2 " " s3
            Loop
        Loop
    Loop
Loop
Close_Output