简体   繁体   中英

AES Encryption - Encryption/Decryption Adding Extra Bytes

I'm writing a simple library for encryption/decryption, using the Framework's objects. The methods are as follows:

public static byte[] Encrypt(byte[] key, byte[] vector, byte[] input)
{
    if(key.Length == 0)
        throw new ArgumentException("Cannot encrypt with empty key");

    if (vector.Length == 0)
        throw new ArgumentException("Cannot encrypt with empty vector");

    if (input.Length == 0)
        throw new ArgumentException("Cannot encrypt empty input");

    var unencryptedBytes = input;

    using(AesCryptoServiceProvider aes = new AesCryptoServiceProvider {Key = key, IV = vector})
    using(ICryptoTransform encryptor = aes.CreateEncryptor())
    using (MemoryStream ms = new MemoryStream())
    using (CryptoStream writer = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
    {
        writer.Write(unencryptedBytes, 0, unencryptedBytes.Length);
        writer.FlushFinalBlock();
        var byteArray = ms.ToArray();

        if(byteArray.Length == 0)
            throw new Exception("Attempted to encrypt but encryption resulted in a byte array of 0 length.");

        return byteArray;
    }
}


public static byte[] Decrypt(byte[] key, byte[] vector, byte[] encrypted)
{
    if (key.Length == 0)
        throw new ArgumentException("Cannot encrypt with empty key");

    if (vector.Length == 0)
        throw new ArgumentException("Cannot encrypt with empty vector");

    if (encrypted == null || encrypted.Length == 0)
        throw new ArgumentException("Cannot decrypt empty or null byte array");

    byte[] unencrypted;

    using (AesCryptoServiceProvider aes = new AesCryptoServiceProvider { Key = key, IV = vector })
    using (ICryptoTransform decryptor = aes.CreateDecryptor(key, vector))
    using (MemoryStream ms = new MemoryStream(encrypted))
    using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
    {

        cs.Read(encrypted, 0, encrypted.Length);

        unencrypted =  ms.ToArray();
    }


    return unencrypted;
}

Feeding it in an encoded string, for example, as follows:

var expected = "This is an example message";

var messageBytes = Encoding.UTF8.GetBytes(expected);

Goes in as (in this example) 26 bytes. After encryption it leaves as 32 bytes, and when this is translated back as a string it has random characters appended at the end but is otherwise perfectly valid, as follows:

"This is an example message�(4���"

Generating a new vector/key changes the random characters that are appended to the end.

How can I eliminate this extra-byte behavior?

EDIT

Included a requested unit test.

[TestMethod]
public void CanEncryptAndDecryptByteArray()
{
    var expected = "This is an example message";

    var messageBytes = Encoding.UTF8.GetBytes(expected);


    AesCryptoServiceProvider aes = new AesCryptoServiceProvider();
    aes.GenerateIV();
    aes.GenerateKey();


    var byteKey = Convert.ToBase64String(aes.Key);
    var vectorKey = Convert.ToBase64String(aes.IV);

    var key = Convert.FromBase64String("Qcf+3VzYNAfPUCfBO/ePSxCLBLItkVfk8ajK86KYebs=");
    var vector = Convert.FromBase64String("aJNKWP7M2D44jilby6BzGg==");

    var encrypted = EncryptionManager.Encrypt(key, vector, messageBytes);
    var decrypted = EncryptionManager.Decrypt(key, vector, encrypted);

    var actual = Encoding.UTF8.GetString(decrypted);

    Assert.AreEqual(expected, actual);
}

The junk-bytes are because we're reading and writing to the same array.

new MemoryStream(encrypted)) Is telling the crypt to read from encrypted .

cs.Read(encrypted, 0, encrypted.Length); Is telling it to write to encrypted .

The solution:

using (AesCryptoServiceProvider aes = new AesCryptoServiceProvider { Key = key, IV = vector })
using (ICryptoTransform decryptor = aes.CreateDecryptor(key, vector))
using (MemoryStream ms = new MemoryStream(encrypted))
using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
{
    var decrypted = new byte[encrypted.Length];
    var bytesRead = cs.Read(decrypted, 0, encrypted.Length);    

    return decrypted.Take(bytesRead).ToArray();
}

Note that we take bytesRead from the array, rather than the full array, as we would otherwise end up with null bytes (which fail a string comparison but appear identical in editors)

The AES encryption is a block encryption with block size of 16 bytes (128-bit) [note: the key size can of 128-bit, 192-bit or 256-bit, but the data block size is of multiplication of 16 bytes]. Thus, to use AES, the resulting encrypted message will be multiplication of 16 - and cannot be otherwise.

To decrypt it, you may want to get rid of the extra bytes it causes too.

  1. One way to do this is by append header in your message, stating how many bytes the message is. Then in the decrypt side, you read this header and cut the number of bytes

     [header = value of 26] + [message] 
  2. Alternatively, you could append some extra bytes at the end of your encrypted message

     [message][append with 000000] //to make up the block, such that the message length will be of multiplication of 16 

Of the two methods above, I personally prefer the first one since it is much clearer, while the second one assumes you receive certain text pattern which is impossible to get concatenated 0000 at the end.

I am not aware if there is a way to solve this issue apart from the two given ways above - implicitly (such as using CryptoStream ) or explicitly.

AES encryption does the encryption in 16, 24 or 32 byte (or 128, 192 or 256 bits) blocks. So the data you get back is just random data packed onto the end.

You'll have to prepend your data with a couple of bytes to represent the actual legnth:

var length = new byte [] { (byte) ((unencryptedBytes.Length >> 8) & 0xff) ,
    (byte) (unencryptedBytes.Length & 0xff);
writer.Write(length, 0, length.Length); 
writer.Write(unencryptedBytes, 0, unencryptedBytes.Length);
writer.FlushFinalBlock();

and then in the reader retrieve the length:

unencrypted =  ms.ToArray();

int length = ((int) unencrypted[0] << 8) + unencrypted[1];

var result = new byte[length];

unencrypted.CopyTo(result, 2, 0, length);

return result;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM