add rc4 tokenizer and key generation per object

This commit is contained in:
Eliot Jones
2019-05-05 15:34:48 +01:00
parent be394f5bba
commit 27928cd3a3
4 changed files with 198 additions and 4 deletions

View File

@@ -0,0 +1,46 @@
namespace UglyToad.PdfPig.Tests.Encryption
{
using System.Linq;
using PdfPig.Encryption;
using PdfPig.Tokens;
using Xunit;
public class RC4Tests
{
[Theory]
[InlineData("Plaintext", "Key", "BBF316E8D940AF0AD3")]
[InlineData("pedia", "Wiki", "1021BF0420")]
[InlineData("Attack at dawn", "Secret", "45A01F645FC35B383552544B9BF5")]
public void Encrypt(string message, string keyText, string cipherTextHex)
{
var data = TextToBytes(message);
var key = TextToBytes(keyText);
var result = RC4.Encrypt(key, data);
var expectedBytes = HexToBytes(cipherTextHex);
Assert.Equal(expectedBytes, result);
var reversed = RC4.Encrypt(key, result);
Assert.Equal(data, reversed);
}
private static byte[] TextToBytes(string text)
{
return text.Select(x => (byte) x).ToArray();
}
private static byte[] HexToBytes(string hex)
{
var result = new byte[hex.Length / 2];
for (var i = 0; i < hex.Length; i += 2)
{
result[i / 2] = HexToken.Convert(hex[i], hex[i + 1]);
}
return result;
}
}
}

View File

@@ -4,7 +4,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Encryption;
using PdfPig.Encryption;
using PdfPig.IO;
using PdfPig.Tokenization.Scanner;
using PdfPig.Tokens;

View File

@@ -32,6 +32,10 @@
[NotNull]
private readonly string password;
private readonly byte[] encryptionKey;
private readonly bool useAes;
public EncryptionHandler(EncryptionDictionary encryptionDictionary, TrailerDictionary trailerDictionary, string password)
{
this.encryptionDictionary = encryptionDictionary;
@@ -63,7 +67,7 @@
? 5
: encryptionDictionary.KeyLength.GetValueOrDefault() / 8;
CalculateKeyRevisions2To4(passwordBytes, ownerKey, (int) encryptionDictionary.UserAccessPermissions, encryptionDictionary.StandardSecurityHandlerRevision,
encryptionKey = CalculateKeyRevisions2To4(passwordBytes, ownerKey, (int) encryptionDictionary.UserAccessPermissions, encryptionDictionary.StandardSecurityHandlerRevision,
length, documentIdBytes, encryptionDictionary.EncryptMetadata);
}
@@ -82,6 +86,87 @@
throw new NotImplementedException($"Encryption is not supported yet. Encryption used in document was: {encryptionDictionary.Dictionary}.");
}
public void Decrypt(IndirectReference reference, IToken token)
{
if (token is StreamToken stream)
{
}
else if (token is StringToken stringToken)
{
}
else if (token is DictionaryToken dictionary)
{
}
else if (token is ArrayToken array)
{
}
}
private byte[] DecryptData(byte[] data, IndirectReference reference)
{
if (useAes && encryptionKey.Length == 32)
{
throw new NotImplementedException("Decryption for AES-256 not currently supported.");
}
var finalKey = GetObjectKey(reference);
if (useAes)
{
throw new NotImplementedException("Decryption for AES-128 not currently supported.");
}
return RC4.Encrypt(finalKey, data);
}
private byte[] GetObjectKey(IndirectReference reference)
{
// 1. Get the object and generation number from the object
// 2. Treating the object and generation number as binary integers extend the
// original n byte encryption key to n + 5 bytes by taking the low-order 3 bytes
// of the object number and the low-order 2 bytes of the generation number, low order
// byte first.
var finalKey = new byte[encryptionKey.Length + 5 + (useAes ? 4 : 0)];
Array.Copy(encryptionKey, finalKey, encryptionKey.Length);
finalKey[encryptionKey.Length] = (byte) reference.ObjectNumber;
finalKey[encryptionKey.Length + 1] = (byte) (reference.ObjectNumber >> 1);
finalKey[encryptionKey.Length + 2] = (byte) (reference.ObjectNumber >> 2);
finalKey[encryptionKey.Length + 3] = (byte) reference.Generation;
finalKey[encryptionKey.Length + 4] = (byte) (reference.Generation >> 1);
// 2. If using the AES algorithm extend the encryption key by 4 bytes by adding the value "sAlT".
if (useAes)
{
finalKey[encryptionKey.Length + 5] = (byte)'s';
finalKey[encryptionKey.Length + 6] = (byte)'A';
finalKey[encryptionKey.Length + 7] = (byte)'l';
finalKey[encryptionKey.Length + 8] = (byte)'T';
}
// 3. Initialize the MD5 hash function and pass the result of 2 as input.
using (var md5 = MD5.Create())
{
md5.ComputeHash(finalKey);
// 4. Use the first (n + 5) bytes (maximum of 16) of the MD5 output as the key for the
// RC4 or AES symmetric key algorithms along with the string or stream data to en/de-crypt
// If using AES the Cipher Block Chaining mode with block size of 16 bytes is used. The
// initialization vector is a 16-byte random number stored as the first 16 bytes of the stream of string.
var length = Math.Min(16, encryptionKey.Length + 5);
var result = new byte[length];
Array.Copy(md5.Hash, result, length);
return result;
}
}
private static bool IsUserPassword(byte[] password, byte[] userKey, byte[] ownerKey, int permissions,
byte[] documentId, int revision, int length, bool encryptMetadata)
{
@@ -141,9 +226,25 @@
// 7. Do the following 50 times: Take the output from the previous MD5 hash and
// pass the first n bytes of the output as input into a new MD5 hash,
// where n is the number of bytes of the encryption key as defined by the value
// of the encryption dictionarys Length entry.
// of the encryption dictionary's Length entry.
if (revision == 3 || revision == 4)
{
var n = length;
return md5.Hash;
var input = md5.Hash;
for (var i = 0; i < 50; i++)
{
md5.ComputeHash(input, 0, n);
input = md5.Hash;
}
}
var result = new byte[length];
Array.Copy(md5.Hash, result, length);
return result;
}
}

View File

@@ -0,0 +1,47 @@
namespace UglyToad.PdfPig.Encryption
{
internal static class RC4
{
public static byte[] Encrypt(byte[] key, byte[] data)
{
// Key-scheduling algorithm
var s = new byte[256];
for (var i = 0; i < 256; i++)
{
s[i] = (byte)i;
}
var j = 0;
for (var i = 0; i < 256; i++)
{
j = (j + s[i] + key[i % key.Length]) % 256;
var temp = s[i];
s[i] = s[j];
s[j] = temp;
}
var result = new byte[data.Length];
// Pseudo-random generation algorithm
{
j = 0;
var i = 0;
for (var step = 0; step < data.Length; step++)
{
i = (i + 1) % 256;
j = (j + s[i]) % 256;
var temp = s[i];
s[i] = s[j];
s[j] = temp;
var k = s[(s[i] + s[j]) % 256];
result[step] = (byte)(data[step] ^ k);
}
}
return result;
}
}
}