using PdfSharpCore.Pdf.Internal; using System; namespace PdfSharpCore.Pdf.Security { class RC4Encryptor : EncryptorBase, IEncryptor { /// /// Bytes used for RC4 encryption. /// readonly byte[] state = new byte[256]; /// /// Creates the encryption Key. /// Based on algorithm #2 (3.2 in Extension Level 3) in the Pdf 1.7 reference (7.6.3.3) /// public virtual void InitEncryptionKey(string password) { var userPad = PadPassword(password); md5.Initialize(); md5.TransformBlock(userPad, 0, userPad.Length, userPad, 0); md5.TransformBlock(ownerValue, 0, ownerValue.Length, ownerValue, 0); var permission = new byte[4]; permission[0] = (byte)pValue; permission[1] = (byte)(pValue >> 8); permission[2] = (byte)(pValue >> 16); permission[3] = (byte)(pValue >> 24); md5.TransformBlock(permission, 0, 4, permission, 0); md5.TransformBlock(documentId, 0, documentId.Length, documentId, 0); if (rValue >= 4 && !encryptMetadata) { var ff = new byte[] { 0xff, 0xff, 0xff, 0xff }; md5.TransformBlock(ff, 0, ff.Length, ff, 0); } md5.TransformFinalBlock(permission, 0, 0); var hash = md5.Hash; if (rValue >= 3) { for (var i = 0; i < 50; i++) { md5.Initialize(); hash = md5.ComputeHash(hash, 0, keyLength); } } encryptionKey = new byte[keyLength]; Array.Copy(hash, encryptionKey, keyLength); } public bool ValidatePassword(string password) { // AESEncryptor sets these if (HaveOwnerPermission || PasswordValid) return true; ValidateOwnerPassword(password); if (!PasswordValid) ValidateUserPassword(password); return PasswordValid; } private void ValidateUserPassword(string password) { CreateUserKey(password); PasswordValid = CompareArrays(computedUserValue, userValue, 16); } private void ValidateOwnerPassword(string password) { var pwdPad = PadPassword(password); md5.Initialize(); var pwdKey = md5.ComputeHash(pwdPad); if (rValue >= 3) { for (var i = 0; i < 50; i++) { pwdKey = md5.ComputeHash(pwdKey, 0, keyLength); } } var n = rValue <= 2 ? 5 : keyLength; var rc4Input = new byte[n]; Array.Copy(pwdKey, rc4Input, n); var ov = new byte[ownerValue.Length]; Array.Copy(ownerValue, ov, ov.Length); if (rValue < 3) { PrepareRC4Key(rc4Input, 0, n); EncryptRC4(ov); } else { var xor = new byte[n]; for (var i = 0; i < 20; i++) { for (var j = 0; j < n; j++) xor[j] = (byte)(rc4Input[j] ^ (19 - i)); PrepareRC4Key(xor, 0, n); EncryptRC4(ov); } } var userPass = PdfEncoders.RawEncoding.GetString(ov); ValidateUserPassword(userPass); if (PasswordValid) HaveOwnerPermission = true; } /// /// Pdf Reference 1.7, Chapter 7.6.3.4, Algorithm #3 /// public void CreateOwnerKey(string password) { var pwdPad = PadPassword(password); md5.Initialize(); var pwdKey = md5.ComputeHash(pwdPad); if (rValue >= 3) { for (var i = 0; i < 50; i++) pwdKey = md5.ComputeHash(pwdKey); } var n = rValue <= 2 ? 5 : keyLength; var rc4Input = new byte[n]; Array.Copy(pwdKey, rc4Input, n); if (rValue >= 3) { for (var i = 0; i < 20; i++) { for (var j = 0; j < rc4Input.Length; j++) rc4Input[j] = (byte)(rc4Input[j] ^ i); PrepareRC4Key(rc4Input); EncryptRC4(pwdPad); } } computedOwnerValue = new byte[n]; Array.Copy(pwdPad, computedOwnerValue, n); } /// /// Pdf Reference 1.7, Chapter 7.6.3.4, Algorithm #4 and #5 /// public void CreateUserKey(string password) { InitEncryptionKey(password); if (rValue == 2) { var data = new byte[passwordPadding.Length]; Array.Copy(passwordPadding, data, data.Length); PrepareRC4Key(encryptionKey); EncryptRC4(data); computedUserValue = new byte[data.Length]; Array.Copy(data, computedUserValue, data.Length); } else { computedUserValue = new byte[32]; md5.Initialize(); md5.TransformBlock(passwordPadding, 0, passwordPadding.Length, passwordPadding, 0); md5.TransformFinalBlock(documentId, 0, documentId.Length); var mkey = md5.Hash; Array.Copy(mkey, computedUserValue, mkey.Length); for (var i = 0; i < 20; i++) { for (var j = 0; j < mkey.Length; j++) mkey[j] = (byte)(encryptionKey[j] ^ i); PrepareRC4Key(mkey); EncryptRC4(computedUserValue, 0, 16); } for (var i = 16; i < 32; i++) computedUserValue[i] = 0; } } /// /// Pdf Reference 1.7, Chapter 7.6.2, Algorithm #1 /// /// public virtual void CreateHashKey(PdfObjectID id) { var objectId = new byte[5]; md5.Initialize(); // Split the object number and generation objectId[0] = (byte)id.ObjectNumber; objectId[1] = (byte)(id.ObjectNumber >> 8); objectId[2] = (byte)(id.ObjectNumber >> 16); objectId[3] = (byte)id.GenerationNumber; objectId[4] = (byte)(id.GenerationNumber >> 8); md5.TransformBlock(encryptionKey, 0, encryptionKey.Length, encryptionKey, 0); // ?? incomplete md5.TransformFinalBlock(objectId, 0, objectId.Length); key = md5.Hash; md5.Initialize(); keySize = encryptionKey.Length + 5; if (keySize > 16) keySize = 16; } public virtual byte[] Encrypt(byte[] bytes) { PrepareRC4Key(key); EncryptRC4(bytes); return bytes; } /// /// Prepare the encryption key. /// protected void PrepareRC4Key(byte[] key) { PrepareRC4Key(key, 0, keySize); } /// /// Prepare the encryption key. /// protected void PrepareRC4Key(byte[] key, int offset, int length) { int idx1 = 0; int idx2 = 0; for (int idx = 0; idx < 256; idx++) this.state[idx] = (byte)idx; byte tmp; for (int idx = 0; idx < 256; idx++) { idx2 = (key[idx1 + offset] + this.state[idx] + idx2) & 255; tmp = this.state[idx]; this.state[idx] = this.state[idx2]; this.state[idx2] = tmp; idx1 = (idx1 + 1) % length; } } /// /// Encrypts the data. /// protected void EncryptRC4(byte[] data) { EncryptRC4(data, 0, data.Length, data); } /// /// Encrypts the data. /// protected void EncryptRC4(byte[] data, int offset, int length) { EncryptRC4(data, offset, length, data); } /// /// Encrypts the data. /// protected void EncryptRC4(byte[] inputData, byte[] outputData) { EncryptRC4(inputData, 0, inputData.Length, outputData); } /// /// Encrypts the data. /// protected void EncryptRC4(byte[] inputData, int offset, int length, byte[] outputData) { length += offset; int x = 0, y = 0; byte b; for (int idx = offset; idx < length; idx++) { x = (x + 1) & 255; y = (this.state[x] + y) & 255; b = this.state[x]; this.state[x] = this.state[y]; this.state[y] = b; outputData[idx] = (byte)(inputData[idx] ^ state[(this.state[x] + this.state[y]) & 255]); } } } }