Home:ALL Converter>Decode cryptlib encoded text using Java (without cryptlib)

Decode cryptlib encoded text using Java (without cryptlib)

Ask Time:2011-05-17T00:49:47         Author:RusH

Json Formatter

Hello.

First of all: I'm new to stackOverflow and to the subject I'm talking about...
I am trying to avoid the usage of the cryptlib library for TripleDES encryption in my Java application (now i am using AES - to ensure the downward compatibility I also want to be able to decode the strings which were created with the cryptlib library but without the useage of JNI).
But none of the things I tried by now worked for me.

Configuration:
Algorithm: TripleDES
Mode: CBC
Format: CRYPT_FORMAT_CRYPTLIB
The key has a size of 16 byte (which is inconvenient, but BouncyCastle would support it).
And the encrypted data has a size which is not a multiple of 8 (for exmaple 81 byte).

In my wrapper of the library (also in C) the context is created this way:

cryptCreateContext( &cryptContext, CRYPT_UNUSED, CRYPT_ALGO_3DES);
cryptSetAttribute( cryptContext, CRYPT_CTXINFO_MODE, CRYPT_MODE_CBC );
cryptSetAttributeString( cryptContext, CRYPT_CTXINFO_KEY, key, keyLen); 

The envelope is created this way:

cryptCreateEnvelope( envelope, CRYPT_FORMAT_CRYPTLIB );

I think the problem is the format (since it is a "cryptlib native" format) - I can not find any description about it in the net...

Currently my best try was this one:

Security.addProvider(new BouncyCastleProvider());
SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), "DESede");
IvParameterSpec iv = new IvParameterSpec(new byte[8]);
Cipher e_cipher = Cipher.getInstance("DESede/CBC/PKCS7Padding", "BC");
e_cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);

byte[] hexdecoded = Hex.decode(ENCRYPTED.getBytes());
byte [] cipherText = e_cipher.doFinal(hexdecoded);

return new String(cipherText);

I also tried different paddings, but I always end in one of these two Exceptions:

  • javax.crypto.IllegalBlockSizeException: data not block size aligned
  • javax.crypto.IllegalBlockSizeException: last block incomplete in decryption

I'm getting a little desperate, so I would be really happy if someone here could help me...

EDIT: Here is the code for the encryption (CryptData.cpp):

bool CCryptData::encryptData(BYTE *key,int keyLen, BYTE *data, int dataLen, int resultMemSize, BYTE *result, int &resultLen)
{
    CRYPT_ENVELOPE cryptEnvelope;
    CRYPT_CONTEXT cryptContext;
    CRYPT_ALGO cryptAlgo = selectCipher( CRYPT_ALGO_3DES );
    int count;

    /* Create the session key context.  We don't check for errors here since
       this code will already have been tested earlier */
    cryptCreateContext( &cryptContext, CRYPT_UNUSED, cryptAlgo );
    cryptSetAttribute( cryptContext, CRYPT_CTXINFO_MODE, CRYPT_MODE_CBC );
    cryptSetAttributeString( cryptContext, CRYPT_CTXINFO_KEY, key, keyLen );

    /* Create the envelope, push in a password and the data, pop the
       enveloped result, and destroy the envelope */
    if( !createEnvelope( &cryptEnvelope ) || \
        !addEnvInfoNumeric( cryptEnvelope, CRYPT_ENVINFO_SESSIONKEY,
                            cryptContext ) )
        return( FALSE );

    cryptSetAttribute( cryptEnvelope, CRYPT_ENVINFO_DATASIZE, dataLen );

    count = pushData( cryptEnvelope, data, dataLen, NULL, 0 );

    if( cryptStatusError( count ) )
        return( FALSE );

    resultLen = popData( cryptEnvelope, result, resultMemSize);

    if( cryptStatusError( count ) )
        return( FALSE );
    if( !destroyEnvelope( cryptEnvelope ) )
        return( FALSE );

    return true;
}

bool CCryptData::checkErrorStatus(int status, CString function)
{
    if( cryptStatusError( status ) )
    {
        m_lastError  = "Error occured in function " + function;
        m_lastError += " with StatusCode: " + status;
        m_bError = true;
        return true;
    }

    return false;
}

int CCryptData::createEnvelope( CRYPT_ENVELOPE *envelope )
{
    int status;

    /* Create the envelope */
    status = cryptCreateEnvelope( envelope, CRYPT_UNUSED, CRYPT_FORMAT_CRYPTLIB );
    if( checkErrorStatus(status, "createEnvelope"))
        return false;

    return( TRUE );
}

int CCryptData::destroyEnvelope( CRYPT_ENVELOPE envelope )
{
    int status;

    /* Destroy the envelope */
    status = cryptDestroyEnvelope( envelope );
    if( checkErrorStatus( status, "destroyEnvelope"))
        return false;

    return( TRUE );

}

int CCryptData::pushData( const CRYPT_ENVELOPE envelope, const BYTE *buffer,
                         const int length, const void *stringEnvInfo,
                         const int numericEnvInfo )
{
    int status, bytesIn;

    /* Push in the data */
    status = cryptPushData( envelope, buffer, length, &bytesIn );
    if( cryptStatusError( status ) )
    {
        printf( "cryptPushData() failed with error code %d, line %d.\n",
                status, __LINE__ );
        return( status );
    }
    if( bytesIn != length )
    {
        printf( "cryptPushData() only copied %d of %d bytes, line %d.\n",
                bytesIn, length, __LINE__ );
        return( SENTINEL );
    }

    /* Flush the data */
    status = cryptPushData( envelope, NULL, 0, NULL );
    if( cryptStatusError( status ) && status != CRYPT_ERROR_COMPLETE )
        {
        printf( "cryptPushData() (flush) failed with error code %d, line "
                "%d.\n", status, __LINE__ );
        return( status );
        }

    return( bytesIn );

}

int CCryptData::popData( CRYPT_ENVELOPE envelope, BYTE *buffer, int bufferSize )
{
    int status, bytesOut;

    status = cryptPopData( envelope, buffer, bufferSize, &bytesOut );
    if( cryptStatusError( status ) )
        {
        printf( "cryptPopData() failed with error code %d, line %d.\n",
                status, __LINE__ );
        return( status );
        }

    return( bytesOut );

}

int CCryptData::addEnvInfoNumeric( const CRYPT_ENVELOPE envelope,
                              const CRYPT_ATTRIBUTE_TYPE type,
                              const int envInfo )
{
    int status;

    status = cryptSetAttribute( envelope, type, envInfo );
    if( checkErrorStatus( status, "addEnvInfoNumeric"))
        return false;

    return( TRUE );

}

CRYPT_ALGO CCryptData::selectCipher( const CRYPT_ALGO algorithm )
{
    if( cryptStatusOK( cryptQueryCapability( algorithm, NULL ) ) )
        return( algorithm );
    return( CRYPT_ALGO_BLOWFISH );
}

EDIT 2: In comparision it is the same implementation as descripted in this mailing list.
But i want to decode it in Java...

EDIT 3: I think the problem is, that the encrypted data is enveloped in cryptlib (as seen in the code).

EDIT 4: I know know that the problem is the enveloping. The cryptlib uses the session key of the created crypt context to envelope the decrypted data in the CRYPT_FORMAT_CRYPTLIB format. The question now is how to decode the envelope before performing the real decryption.
Any suggestions how i could perform the decoding?

Author:RusH,eproduced under the CC 4.0 BY-SA copyright license with a link to the original source and this disclaimer.
Link to original article:https://stackoverflow.com/questions/6020648/decode-cryptlib-encoded-text-using-java-without-cryptlib
RusH :

I finally got it.\nAfter debugging the source of cryptlib, I found out that the encrypted text is HEX encoded CMS enveloped content.\nI used this online decoder to analyze the ASN.1 sequence (CMS uses ASN.1 notation):\nhttp://www.aggressivesoftware.com/tools/asn1decoder.php\n\nAfter that I could reproduce the decoding and decryption in Java using BouncyCastle as provider:\n\n\n public byte[] decrypt(final byte[] data) throws CryptoException {\n try {\n Security.addProvider(new BouncyCastleProvider());\n\n EncryptedContentInfo encryptionInfo = parseContentInfo(data);\n AlgorithmIdentifier algoID = encryptionInfo.getContentEncryptionAlgorithm();\n\n // get the real encrypted data\n byte[] encryptedData = encryptionInfo.getEncryptedContent().getOctets();\n\n // extract the initialization vector from the algorithm identifier object\n byte[] ivBytes = ((ASN1OctetString) algoID.getParameters()).getOctets();\n // create the key depending on the algorithm\n SecretKeySpec keySpec = new SecretKeySpec(rawKey, algoID.getObjectId().getId());\n // request cipher\n Cipher c = Cipher.getInstance(algoID.getObjectId().getId(), CRYPT_PROVIDER);\n\n c.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(ivBytes));\n byte[] decrypted = c.doFinal(encryptedData);\n\n return decrypted;\n\n } catch (NoSuchAlgorithmException e) {\n throw new CryptoException(e);\n } catch (NoSuchProviderException e) {\n throw new CryptoException(e);\n } catch (NoSuchPaddingException e) {\n throw new CryptoException(e);\n } catch (InvalidKeyException e) {\n throw new CryptoException(e);\n } catch (InvalidAlgorithmParameterException e) {\n throw new CryptoException(e);\n } catch (IllegalBlockSizeException e) {\n throw new CryptoException(e);\n } catch (BadPaddingException e) {\n throw new CryptoException(e);\n }\n }\n}\n\n\n\n\n\n\n private EncryptedContentInfo parseContentInfo(final byte[] encrypted) throws CryptoException {\n try {\n // create a new byte array stream\n ByteArrayInputStream bin = new ByteArrayInputStream(encrypted);\n // create an ASN.1 input stream\n ASN1InputStream ain = new ASN1InputStream(bin);\n\n // read the whole sequence\n ASN1Sequence mainSequence = (ASN1Sequence) ain.readObject();\n // check if it is an encrypted data\n DERObjectIdentifier mainIdentifier = (DERObjectIdentifier) mainSequence\n .getObjectAt(ASN1IDENTIFIER_ID);\n if (!mainIdentifier.equals(OID_ENCRYPTED_DATA)) {\n throw new CryptoException(\"Given data is not encrypted CMS.\");\n }\n // parse the encrypted object\n DERTaggedObject encryptedObject = (DERTaggedObject) mainSequence.getObjectAt(ASN1CONTENT_ID);\n // parse the sequence containing the useful informations\n ASN1Sequence encryptedSequence = (ASN1Sequence) encryptedObject.getObject();\n // create the content info object\n EncryptedContentInfo info = EncryptedContentInfo.getInstance(encryptedSequence\n .getObjectAt(ASN1CONTENT_ID));\n\n return info;\n } catch (IOException e) {\n // if the main sequence can not be read from the stream an IOException would be thrown\n throw new CryptoException(e);\n } catch (ClassCastException e) {\n // if the parsing fails, a ClassCastException would be thrown\n throw new CryptoException(e);\n } catch (IllegalStateException e) {\n // if the parsing fails, also a IllegalStateException can be thrown\n throw new CryptoException(e);\n }\n }\n\n\n\nThis way I was able to decode the given text, identify the real decryption algorithm and extract the used initialization vector and the real decrypted data to perform the decryption.",
2011-05-30T08:54:37
yy