📄 standardsecurity.cs
字号:
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Security.Cryptography;
using QiHe.CodeLib;
namespace AnotherPDFLib
{
/// <summary>
/// Standard Security Handler, the built-in password-based security handler.
/// </summary>
public class StandardSecurity
{
/// <summary>
/// compute the encryption key if password is valid
/// </summary>
/// <param name="password">either user password or owner password</param>
/// <param name="usercheck"></param>
/// <param name="ownercheck"></param>
/// <param name="permission"></param>
/// <param name="fileID"></param>
/// <param name="encryptMetadata"></param>
/// <param name="revision"></param>
/// <param name="keyLength"></param>
/// <returns></returns>
public static byte[] ComputeKey(
string password,
byte[] usercheck,
byte[] ownercheck,
uint permission,
byte[] fileID,
bool encryptMetadata,
int revision,
int keyLength)
{
byte[] passwordBytes = PadPassword(password);
byte[] key = ComputeKey(passwordBytes, ownercheck, permission, fileID, encryptMetadata, revision, keyLength);
if (!CheckPassword(usercheck, key, fileID, revision))
{
//guess the password is owner password
passwordBytes = ComputeUserPasswordFromOwnerPassword(ownercheck, password, keyLength, revision);
key = ComputeKey(passwordBytes, ownercheck, permission, fileID, encryptMetadata, revision, keyLength);
if (!CheckPassword(usercheck, key, fileID, revision))
{
throw new Exception("password is invalid.");
}
}
return key;
}
public static byte[] CreateKey(
string password,
byte[] ownercheck,
uint permission,
byte[] fileID,
bool encryptMetadata,
int revision,
int keyLength)
{
byte[] passwordBytes = PadPassword(password);
byte[] key = ComputeKey(passwordBytes, ownercheck, permission, fileID, encryptMetadata, revision, keyLength);
return key;
}
static byte[] PaddingBytes = new byte[32] {
0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A};
/// <summary>
/// Algorithm 3.2 Computing an encryption key
/// </summary>
private static byte[] ComputeKey(byte[] passwordBytes, byte[] ownercheckBytes, uint permission, byte[] fileID, bool encryptMetadata, int revision, int keyLength)
{
MemoryStream data = new MemoryStream();
BinaryWriter writer = new BinaryWriter(data);
//Pad or truncate the password string to exactly 32 bytes.
writer.Write(passwordBytes);
//Pass the value of the encryption dictionary's O entry to the MD5 hash function.
writer.Write(ownercheckBytes);
//Treat the value of the P entry as an unsigned 4-byte integer and
//pass these bytes to the MD5 hash function, low-order byte first.
byte[] permissionBytes = BitConverter.GetBytes(permission);
writer.Write(permissionBytes);
//Pass the first element of the file’s file identifier array.
writer.Write(fileID);
//If document metadata is not being encrypted, pass 4 bytes with the value 0xFFFFFFFF
if (!encryptMetadata)
{
writer.Write(0xFFFFFFFF);
}
writer.Flush();
data.Position = 0;
//Finish the hash.
System.Security.Cryptography.MD5 md5 = new MD5CryptoServiceProvider();
byte[] hash = md5.ComputeHash(data);
//(Revision 3 or greater) Do the following 50 times:
if (revision >= 3)
{
for (int i = 0; i < 50; i++)
{
hash = md5.ComputeHash(hash, 0, keyLength);
}
}
//Set the encryption key to the first n bytes of the output from the final MD5 hash
return CreateKey(hash, keyLength);
}
private static byte[] CreateKey(byte[] hash, int keyLength)
{
if (keyLength == hash.Length)
{
return hash;
}
else
{
byte[] key = new byte[keyLength];
Array.Copy(hash, key, keyLength);
return key;
}
}
private static byte[] PadPassword(string password)
{
byte[] passwordBytes = new byte[32];
byte[] chars = Encoding.ASCII.GetBytes(password);
int pwdlen = chars.Length;
if (pwdlen >= 32)
{
Array.Copy(chars, passwordBytes, 32);
}
else
{
Array.Copy(chars, passwordBytes, pwdlen);
Array.Copy(PaddingBytes, 0, passwordBytes, pwdlen, 32 - pwdlen);
}
return passwordBytes;
}
/// <summary>
/// Algorithm 3.3 Computing the encryption dictionary's O (owner password) value
/// </summary>
/// <param name="userpassword"></param>
/// <param name="ownerpassword"></param>
/// <param name="key"></param>
/// <param name="fileID"></param>
/// <param name="revision"></param>
/// <returns></returns>
public static byte[] CreateOwnerCheck(
string userpassword,
string ownerpassword,
int keyLength,
int revision)
{
byte[] key = ComputeOwnerKey(userpassword, ownerpassword, keyLength, revision);
byte[] ownercheck = RC4.EncryptDecrypt(PadPassword(userpassword), key);
//(Revision 3 or greater) Do the following 19 times:
if (revision >= 3)
{
for (int i = 1; i <= 19; i++)
{
byte[] newkey = XOR(key, i);
ownercheck = RC4.EncryptDecrypt(ownercheck, newkey);
}
}
return ownercheck;
}
private static byte[] ComputeOwnerKey(string userpassword, string ownerpassword, int keyLength, int revision)
{
byte[] passwordBytes = ownerpassword == null ?
PadPassword(userpassword) : PadPassword(ownerpassword);
System.Security.Cryptography.MD5 md5 = new MD5CryptoServiceProvider();
byte[] hash = md5.ComputeHash(passwordBytes);
//(Revision 3 or greater) Do the following 50 times:
if (revision >= 3)
{
for (int i = 0; i < 50; i++)
{
hash = md5.ComputeHash(hash);
}
}
//4. Create an RC4 encryption key
return CreateKey(hash, keyLength);
}
/// <summary>
/// Computing the encryption dictionary's U (user password) value
/// Algorithm 3.4 (Revision 2)
/// Algorithm 3.5 (Revision 3 or greater)
/// </summary>
/// <param name="key"></param>
/// <param name="fileID"></param>
/// <param name="revision"></param>
/// <returns></returns>
public static byte[] CreateUserCheck(byte[] key, byte[] fileID, int revision)
{
if (revision == 2)
{
return RC4.EncryptDecrypt(PaddingBytes, key);
}
else
{
byte[] data = new byte[PaddingBytes.Length + fileID.Length];
PaddingBytes.CopyTo(data, 0);
fileID.CopyTo(data, PaddingBytes.Length);
System.Security.Cryptography.MD5 md5 = new MD5CryptoServiceProvider();
byte[] hash = md5.ComputeHash(data);
hash = RC4.EncryptDecrypt(hash, key);
for (int i = 1; i <= 19; i++)
{
byte[] newkey = XOR(key, i);
hash = RC4.EncryptDecrypt(hash, newkey);
}
byte[] usercheck = new byte[32];
hash.CopyTo(usercheck, 0);
SecretKeys.GenerateKey(16).CopyTo(usercheck, 16);
return usercheck;
}
}
private static byte[] XOR(byte[] key, int n)
{
byte[] newkey = new byte[key.Length];
for (int i = 0; i < key.Length; i++)
{
newkey[i] = (byte)(key[i] ^ n);
}
return newkey;
}
/// <summary>
/// Algorithm 3.6 Authenticating the user password
/// </summary>
/// <param name="usercheck"></param>
/// <param name="key"></param>
/// <param name="fileID"></param>
/// <param name="revision"></param>
/// <returns></returns>
private static bool CheckPassword(byte[] usercheck, byte[] key, byte[] fileID, int revision)
{
byte[] usercheckComputed = CreateUserCheck(key, fileID, revision);
if (revision == 2)
{
return Algorithm.ArrayEqual(usercheck, usercheckComputed, 32);
}
else
{
return Algorithm.ArrayEqual(usercheck, usercheckComputed, 16);
}
}
/// <summary>
/// Algorithm 3.7 Authenticating the owner password
/// </summary>
/// <returns></returns>
private static byte[] ComputeUserPasswordFromOwnerPassword(
byte[] ownercheck,
string ownerpassword,
int keyLength,
int revision)
{
byte[] key = ComputeOwnerKey("", ownerpassword, keyLength, revision);
byte[] userpassword = ownercheck;
if (revision == 2)
{
userpassword = RC4.EncryptDecrypt(ownercheck, key);
}
else
{
for (int i = 19; i >= 0; i--)
{
byte[] newkey = XOR(key, i);
userpassword = RC4.EncryptDecrypt(userpassword, newkey);
}
}
return userpassword;
}
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -