5

I have a file which has been encrypted by des.exe.

A file can be encrypted and decrypted using the following commands:

des -E -k "foo" sample.txt sample.txt.enc
des -D -k "foo" sample.txt.enc sample.txt.dec

I have attempted to decrypt using the following:

public byte[] Decrypt(FileInfo file, string key)
{
  byte[] keyAsBytes = LibDesPasswordConvertor.PasswordToKey(key);
  byte[] initializationVector = keyAsBytes;

  var cryptoProvider = new DESCryptoServiceProvider();  
  cryptoProvider.Mode = CipherMode.CBC;
  cryptoProvider.Padding = PaddingMode.None;  

  using (FileStream fs = file.OpenRead())
  using (var memStream = new MemoryStream())
  using (var decryptor = cryptoProvider.CreateDecryptor(keyAsBytes, initializationVector))
  using (var cryptoStream = new CryptoStream(memStream, decryptor, CryptoStreamMode.Write))
  {
    fs.CopyTo(cryptoStream);
    fs.Flush();
    cryptoStream.FlushFinalBlock();

    return memStream.ToArray();
  }
}

public static class LibDesPasswordConvertor
{
  public static byte[] PasswordToKey(string password)
  {
    if (string.IsNullOrWhiteSpace(password)) 
    { 
      throw new ArgumentException("password"); 
    }

    var key = new byte[8];

    for (int i = 0; i < password.Length; i++)
    {
      var c = (int)password[i];
      if ((i % 16) < 8)
      {
        key[i % 8] ^= (byte)(c << 1);
      }
      else
      {
        // reverse bits e.g. 11010010 -> 01001011
        c = (((c << 4) & 0xf0) | ((c >> 4) & 0x0f));
        c = (((c << 2) & 0xcc) | ((c >> 2) & 0x33));
        c = (((c << 1) & 0xaa) | ((c >> 1) & 0x55));
        key[7 - (i % 8)] ^= (byte)c;
      }
    }

    AddOddParity(key);

    var target = new byte[8];
    var passwordBuffer = Encoding.ASCII.GetBytes(password).Concat(new byte[8]).Take(password.Length + (8 - (password.Length % 8)) % 8).ToArray();

    using(var des = DES.Create())
    using(var encryptor = des.CreateEncryptor(key, key))
    {
      for (int x = 0; x < passwordBuffer.Length / 8; ++x)
      {
        encryptor.TransformBlock(passwordBuffer, 8 * x, 8, target, 0);
      }
    }

    AddOddParity(target);

    return target;
  }


  private static void AddOddParity(byte[] buffer)
  {
    for (int i = 0; i < buffer.Length; ++i)
    {
      buffer[i] = _oddParityTable[buffer[i]];
    }
  }

  private static byte[] _oddParityTable = {
          1,  1,  2,  2,  4,  4,  7,  7,  8,  8, 11, 11, 13, 13, 14, 14,
         16, 16, 19, 19, 21, 21, 22, 22, 25, 25, 26, 26, 28, 28, 31, 31,
         32, 32, 35, 35, 37, 37, 38, 38, 41, 41, 42, 42, 44, 44, 47, 47,
         49, 49, 50, 50, 52, 52, 55, 55, 56, 56, 59, 59, 61, 61, 62, 62,
         64, 64, 67, 67, 69, 69, 70, 70, 73, 73, 74, 74, 76, 76, 79, 79,
         81, 81, 82, 82, 84, 84, 87, 87, 88, 88, 91, 91, 93, 93, 94, 94,
         97, 97, 98, 98,100,100,103,103,104,104,107,107,109,109,110,110,
        112,112,115,115,117,117,118,118,121,121,122,122,124,124,127,127,
        128,128,131,131,133,133,134,134,137,137,138,138,140,140,143,143,
        145,145,146,146,148,148,151,151,152,152,155,155,157,157,158,158,
        161,161,162,162,164,164,167,167,168,168,171,171,173,173,174,174,
        176,176,179,179,181,181,182,182,185,185,186,186,188,188,191,191,
        193,193,194,194,196,196,199,199,200,200,203,203,205,205,206,206,
        208,208,211,211,213,213,214,214,217,217,218,218,220,220,223,223,
        224,224,227,227,229,229,230,230,233,233,234,234,236,236,239,239,
        241,241,242,242,244,244,247,247,248,248,251,251,253,253,254,254};
}

But when I execute:

const string KEY = "foo";  
var utf8Bytes = Decrypt(new FileInfo(@"PATH-TO\sample.txt.enc"), KEY);

I get:

�1D���z+�a Sample.y���0F�01

Original text:

This is a Sample.

Encrypted:

ñGYjl¦ûg†¼64©‹Bø
é¯Kœ|
MaYaN
  • 6,683
  • 12
  • 57
  • 109

1 Answers1

4

To my surprise you've already derived the key correctly. That was the meat of the problem, so Kudos for solving that part already. That the key is correct becomes clear when you see that part of the plaintext is present in the decryption - it wouldn't if the key was wrong.


Looking into the source and some docs from times past, I found a likely IV of all zeros instead of reusing the key bytes (both are very wrong, in cryptographic terms).

Furthermore, as always for SSLeay, the ECB and CBC modes use PKCS#7 compatible padding, rather than no padding.


Finally, FlushFinalBlock will be automatically called if you close the stream, e.g. by exiting the try-with-resources. So if you get the array afterwards then you should get the right values - after you unpad correctly, of course. If you call Flush then FlushFinalBlock will already be called, and calling it twice will make a mess out of things.

Simply removing the flush calls and retrieving the array after the CryptoStream is closed is the way to go.


Both DES and the key derivation (des_string_to_key and des_string_to_2keys) that Young copied from MIT are completely insecure. Using an all zero IV is wrong.

If you use this as transport mode than padding oracles will apply, and decryption is not even necessary for an attacker. The ciphertext is not integrity protected.

If you use the above routines to keep anything confidential or secure you're fooling yourself. This is 80s technology, and I think that real cryptographers wouldn't find it secure back then either.

Basically if your attacker is over 8 years old, you're in trouble.

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
  • Excellent, thank you so much for your help. Sadly we have no control over the incoming files otherwise would definitely stay away from DES. – MaYaN Jan 16 '20 at 14:00
  • You're welcome. Fun to see that key derivation implemented, I'll remember that :) Actually, I'm stealing it and re-implement in Java later. – Maarten Bodewes Jan 16 '20 at 14:03
  • @MaYaN how did you handle the padding mode at the end? I get some extra chars when decrypting as well. Looks like des.exe does not use a standard PaddingMode. – Anthony Serrano Bianco Oct 04 '21 at 22:27
  • I don't remember having to do anything specific for that...I do remember however that I had to flush the stream manually. – MaYaN Oct 06 '21 at 10:51
  • @MaYaN thanks for answering. So you used PaddingMode.PKCS7 as suggested my MaartenBodewes or used PaddingMode.None? Also, the stream you manually flused was the cryptoStream, right? – Anthony Serrano Bianco Oct 06 '21 at 13:13
  • I don't remember unfortunately I no longer have access to the code :-( – MaYaN Oct 12 '21 at 13:20