private void PrepareEncryptionDictAES(PdfEncryption encryptionDictionary, PdfName aesVName) { var cryptFilterDictionary = new PdfCryptFilterDictionary(encryptionDictionary.File); cryptFilterDictionary.CryptFilterMethod = aesVName; cryptFilterDictionary.Length = keyLength; encryptionDictionary.StdCryptFilterDictionary = cryptFilterDictionary; encryptionDictionary.StreamFilterName = PdfName.StdCF; encryptionDictionary.StringFilterName = PdfName.StdCF; IsAES = true; }
/** * Prepare document for encryption. * * @param document The document to encrypt. * * @ If there is an error accessing data. */ public override void PrepareDocumentForEncryption(Document document) { PdfEncryption encryptionDictionary = document.File.Encryption; if (encryptionDictionary == null) { encryptionDictionary = new PdfEncryption(document.File); } int version = ComputeVersionNumber(); int revision = ComputeRevisionNumber(version); encryptionDictionary.Filter = FILTER; encryptionDictionary.Version = version; if (version != 4 && version != 5) { // remove CF, StmF, and StrF entries that may be left from a previous encryption encryptionDictionary.RemoveV45filters(); } encryptionDictionary.Revision = revision; encryptionDictionary.Length = keyLength; string ownerPassword = policy.OwnerPassword ?? string.Empty; string userPassword = policy.UserPassword ?? string.Empty; // If no owner password is set, use the user password instead. if (ownerPassword.Length == 0) { ownerPassword = userPassword; } int permissionInt = policy.Permissions.PermissionBytes; encryptionDictionary.Permissions = permissionInt; int length = keyLength / 8; if (revision == 6) { // PDFBOX-4155 ownerPassword = SaslPrep.SaslPrepStored(ownerPassword); userPassword = SaslPrep.SaslPrepStored(userPassword); PrepareEncryptionDictRev6(ownerPassword, userPassword, encryptionDictionary, permissionInt); } else { PrepareEncryptionDictRev2345(ownerPassword, userPassword, encryptionDictionary, permissionInt, document, revision, length); } document.File.Encryption = encryptionDictionary; }
private void PrepareEncryptionDictRev2345(string ownerPassword, string userPassword, PdfEncryption encryptionDictionary, int permissionInt, Document document, int revision, int length) { var idArray = document.File.ID; //check if the document has an id yet. If it does not then generate one if (idArray == null || idArray.BaseDataObject.Count < 2) { DateTime Jan1st1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); using (var md = MD5.Create()) { BigInteger time = new BigInteger((DateTime.UtcNow - Jan1st1970).TotalMilliseconds.ToString()); md.Update(time.ToByteArray()); md.Update(Charset.ISO88591.GetBytes(ownerPassword)); md.Update(Charset.ISO88591.GetBytes(userPassword)); md.Update(Charset.ISO88591.GetBytes(document.Information.ToString())); var finBlock = Charset.ISO88591.GetBytes(this.ToString()); PdfString idString = new PdfString(md.Digest(finBlock)); idArray = new FileIdentifier(); idArray.BaseID = idString; idArray.VersionID = idString; document.File.ID = idArray; } } PdfString id = idArray.BaseID; byte[] ownerBytes = ComputeOwnerPassword( Charset.ISO88591.GetBytes(ownerPassword), Charset.ISO88591.GetBytes(userPassword), revision, length); byte[] userBytes = ComputeUserPassword( Charset.ISO88591.GetBytes(userPassword), ownerBytes, permissionInt, id.GetBuffer(), revision, length, true); encryptionKey = ComputeEncryptedKey(Charset.ISO88591.GetBytes(userPassword), ownerBytes, null, null, null, permissionInt, id.GetBuffer(), revision, length, true, false); encryptionDictionary.OwnerKey = ownerBytes; encryptionDictionary.UserKey = userBytes; if (revision == 4) { PrepareEncryptionDictAES(encryptionDictionary, PdfName.AESV2); } }
// Algorithm 13: validate permissions ("Perms" field). Relaxed to accommodate buggy encoders // https://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/adobe_supplement_iso32000.pdf private void ValidatePerms(PdfEncryption encryption, int dicPermissions, bool encryptMetadata) { try { // "Decrypt the 16-byte Perms string using AES-256 in ECB mode with an // initialization vector of zero and the file encryption key as the key." //@SuppressWarnings({ "squid:S4432"}) byte[] perms = null; using (var cipher = new RijndaelManaged()) { cipher.Mode = CipherMode.ECB; cipher.Key = encryptionKey; cipher.Padding = PaddingMode.None; using (var decriptor = cipher.CreateDecryptor()) { perms = decriptor.DoFinal(encryption.Perms); } } // "Verify that bytes 9-11 of the result are the characters ‘a’, ‘d’, ‘b’." if (perms[9] != 'a' || perms[10] != 'd' || perms[11] != 'b') { Debug.WriteLine("warn: Verification of permissions failed (constant)"); } // "Bytes 0-3 of the decrypted Perms entry, treated as a little-endian integer, // are the user permissions. They should match the value in the P key." int permsP = perms[0] & 0xFF | (perms[1] & 0xFF) << 8 | (perms[2] & 0xFF) << 16 | (perms[3] & 0xFF) << 24; if (permsP != dicPermissions) { Debug.WriteLine($"warn: Verification of permissions failed ({$"{permsP:X8}"} != {$"{dicPermissions:X8}"})"); } if (encryptMetadata && perms[8] != 'T' || !encryptMetadata && perms[8] != 'F') { Debug.WriteLine("warn: Verification of permissions failed (EncryptMetadata)"); } } catch (Exception e) { LogIfStrongEncryptionMissing(); throw new IOException("ValidatePerms", e); } }
private void PrepareEncryptionDictAES(PdfEncryption encryptionDictionary, PdfName aesVName, byte[][] recipients) { PdfCryptFilterDictionary cryptFilterDictionary = new PdfCryptFilterDictionary(encryptionDictionary.File); cryptFilterDictionary.CryptFilterMethod = aesVName; cryptFilterDictionary.Length = keyLength; PdfArray array = new PdfArray(); foreach (byte[] recipient in recipients) { array.Add(new PdfString(recipient)); } cryptFilterDictionary.BaseDataObject[PdfName.Recipients] = array; //array.setDirect(true); encryptionDictionary.DefaultCryptFilterDictionary = cryptFilterDictionary; encryptionDictionary.StreamFilterName = PdfName.DefaultCryptFilter; encryptionDictionary.StringFilterName = PdfName.DefaultCryptFilter; //cryptFilterDictionary.getCOSObject().setDirect(true); IsAES = true; }
/** * Prepares everything to decrypt the document. * * @param encryption encryption dictionary, can be retrieved via * {@link Document#getEncryption()} * @param documentIDArray document id which is returned via * {@link org.apache.pdfbox.cos.COSDocument#getDocumentID()} (not used by * this handler) * @param decryptionMaterial Information used to decrypt the document. * * @throws IOException If there is an error accessing data. If verbose mode * is enabled, the exception message will provide more details why the * match wasn't successful. */ public override void PrepareForDecryption(PdfEncryption encryption, PdfArray documentIDArray, DecryptionMaterial decryptionMaterial) { if (!(decryptionMaterial is PublicKeyDecryptionMaterial)) { throw new IOException( "Provided decryption material is not compatible with the document"); } SetDecryptMetadata(encryption.IsEncryptMetaData); if (encryption.Length != 0) { this.keyLength = encryption.Length; } PublicKeyDecryptionMaterial material = (PublicKeyDecryptionMaterial)decryptionMaterial; try { bool foundRecipient = false; //Org.BouncyCastle.X509.Extension. X509Certificate certificate = material.Certificate; X509CertificateEntry materialCert = null; if (certificate != null) { materialCert = new X509CertificateEntry(certificate); } // the decrypted content of the enveloped data that match // the certificate in the decryption material provided byte[] envelopedData = null; // the bytes of each recipient in the recipients array PdfArray array = (PdfArray)encryption.BaseDataObject.Resolve(PdfName.Recipients); if (array == null) { PdfCryptFilterDictionary defaultCryptFilterDictionary = encryption.DefaultCryptFilterDictionary; array = (PdfArray)defaultCryptFilterDictionary.BaseDataObject.Resolve(PdfName.Recipients); } byte[][] recipientFieldsBytes = new byte[array.Count][]; //TODO encryption.getRecipientsLength() and getRecipientStringAt() should be deprecated int recipientFieldsLength = 0; StringBuilder extraInfo = new StringBuilder(); for (int i = 0; i < array.Count; i++) { PdfString recipientFieldString = (PdfString)array.Resolve(i); byte[] recipientBytes = recipientFieldString.GetBuffer(); CmsEnvelopedData data = new CmsEnvelopedData(recipientBytes); var recipCertificatesIt = data.GetRecipientInfos().GetRecipients(); int j = 0; foreach (RecipientInformation ri in recipCertificatesIt) { // Impl: if a matching certificate was previously found it is an error, // here we just don't care about it RecipientID rid = ri.RecipientID; if (!foundRecipient && rid.Match(materialCert)) { foundRecipient = true; var privateKey = material.PrivateKey; // might need to call setContentProvider() if we use PKI token, see // http://bouncy-castle.1462172.n4.nabble.com/CMSException-exception-unwrapping-key-key-invalid-unknown-key-type-passed-to-RSA-td4658109.html //DotNetUtilities.GetKeyPair(ri.AlgorithmIdentifier) envelopedData = ri.GetContent(privateKey.Key); break; } j++; if (certificate != null) { extraInfo.Append('\n'); extraInfo.Append(j); extraInfo.Append(": "); if (ri is KeyTransRecipientInformation) { appendCertInfo(extraInfo, (KeyTransRecipientInformation)ri, certificate, materialCert); } } } recipientFieldsBytes[i] = recipientBytes; recipientFieldsLength += recipientBytes.Length; } if (!foundRecipient || envelopedData == null) { throw new IOException("The certificate matches none of " + array.Count + " recipient entries" + extraInfo.ToString()); } if (envelopedData.Length != 24) { throw new IOException("The enveloped data does not contain 24 bytes"); } // now envelopedData contains: // - the 20 bytes seed // - the 4 bytes of permission for the current user byte[] accessBytes = new byte[4]; Array.Copy(envelopedData, 20, accessBytes, 0, 4); AccessPermission currentAccessPermission = new AccessPermission(accessBytes); currentAccessPermission.IsReadOnly = true; CurrentAccessPermission = currentAccessPermission; // what we will put in the SHA1 = the seed + each byte contained in the recipients array byte[] sha1Input = new byte[recipientFieldsLength + 20]; // put the seed in the sha1 input Array.Copy(envelopedData, 0, sha1Input, 0, 20); // put each bytes of the recipients array in the sha1 input int sha1InputOffset = 20; foreach (byte[] recipientFieldsByte in recipientFieldsBytes) { Array.Copy(recipientFieldsByte, 0, sha1Input, sha1InputOffset, recipientFieldsByte.Length); sha1InputOffset += recipientFieldsByte.Length; } byte[] mdResult; if (encryption.Version == 4 || encryption.Version == 5) { mdResult = SHA256.Create().Digest(sha1Input); // detect whether AES encryption is used. This assumes that the encryption algo is // stored in the PDCryptFilterDictionary // However, crypt filters are used only when V is 4 or 5. PdfCryptFilterDictionary defaultCryptFilterDictionary = encryption.DefaultCryptFilterDictionary; if (defaultCryptFilterDictionary != null) { PdfName cryptFilterMethod = defaultCryptFilterDictionary.CryptFilterMethod; IsAES = PdfName.AESV2.Equals(cryptFilterMethod) || PdfName.AESV3.Equals(cryptFilterMethod); } } else { mdResult = SHA1.Create().Digest(sha1Input); } // we have the encryption key ... encryptionKey = new byte[this.keyLength / 8]; Array.Copy(mdResult, 0, encryptionKey, 0, this.keyLength / 8); } catch (Exception e) { throw new IOException("", e); } }
/** * Prepare the document for encryption. * * @param doc The document that will be encrypted. * * @throws IOException If there is an error while encrypting. */ public override void PrepareDocumentForEncryption(Document doc) { try { PdfEncryption dictionary = doc.File.Encryption; if (dictionary == null) { dictionary = new PdfEncryption(doc.File); } dictionary.Filter = FILTER; dictionary.Length = this.keyLength; int version = ComputeVersionNumber(); dictionary.Version = version; // remove CF, StmF, and StrF entries that may be left from a previous encryption dictionary.RemoveV45filters(); // create the 20 bytes seed byte[] seed = new byte[20]; CipherKeyGenerator key; try { key = GeneratorUtilities.GetKeyGenerator("AES"); } catch (Exception e) { // should never happen throw new Exception("AES Key Generator", e); } key.Init(new KeyGenerationParameters(new SecureRandom(), 192)); var sk = key.GenerateKey(); // create the 20 bytes seed Array.Copy(sk, 0, seed, 0, 20); byte[][] recipientsFields = ComputeRecipientsField(seed); int shaInputLength = seed.Length; foreach (byte[] field in recipientsFields) { shaInputLength += field.Length; } byte[] shaInput = new byte[shaInputLength]; Array.Copy(seed, 0, shaInput, 0, 20); int shaInputOffset = 20; foreach (byte[] recipientsField in recipientsFields) { Array.Copy(recipientsField, 0, shaInput, shaInputOffset, recipientsField.Length); shaInputOffset += recipientsField.Length; } byte[] mdResult; if (version == 4 || version == 5) { dictionary.SubFilter = SUBFILTER5; mdResult = SHA256.Create().Digest(shaInput); PdfName aesVName = version == 5 ? PdfName.AESV3 : PdfName.AESV2; PrepareEncryptionDictAES(dictionary, aesVName, recipientsFields); } else { dictionary.SubFilter = SUBFILTER4; mdResult = SHA1.Create().Digest(shaInput); dictionary.SetRecipients(recipientsFields); } this.encryptionKey = new byte[this.keyLength / 8]; Array.Copy(mdResult, 0, this.encryptionKey, 0, this.keyLength / 8); doc.File.Encryption = dictionary; } catch (Exception e) { throw new IOException("", e); } }
private void PrepareEncryptionDictRev6(string ownerPassword, string userPassword, PdfEncryption encryptionDictionary, int permissionInt) { try { SecureRandom rnd = new SecureRandom(); using (var cipher = new RijndaelManaged()) { cipher.Mode = CipherMode.CBC; cipher.Padding = PaddingMode.None; // make a random 256-bit file encryption key encryptionKey = new byte[32]; rnd.NextBytes(encryptionKey); // Algorithm 8a: Compute U byte[] userPasswordBytes = Truncate127(Charset.UTF8.GetBytes(userPassword)); byte[] userValidationSalt = new byte[8]; byte[] userKeySalt = new byte[8]; rnd.NextBytes(userValidationSalt); rnd.NextBytes(userKeySalt); byte[] hashU = ComputeHash2B(Concat(userPasswordBytes, userValidationSalt), userPasswordBytes, null); byte[] u = Concat(hashU, userValidationSalt, userKeySalt); // Algorithm 8b: Compute UE byte[] hashUE = ComputeHash2B(Concat(userPasswordBytes, userKeySalt), userPasswordBytes, null); byte[] ue = null; using (var enciptor = cipher.CreateEncryptor(hashUE, new byte[16]))// "an initialization vector of zero" { ue = enciptor.DoFinal(encryptionKey); } // Algorithm 9a: Compute O byte[] ownerPasswordBytes = Truncate127(Charset.UTF8.GetBytes(ownerPassword)); byte[] ownerValidationSalt = new byte[8]; byte[] ownerKeySalt = new byte[8]; rnd.NextBytes(ownerValidationSalt); rnd.NextBytes(ownerKeySalt); byte[] hashO = ComputeHash2B(Concat(ownerPasswordBytes, ownerValidationSalt, u), ownerPasswordBytes, u); byte[] o = Concat(hashO, ownerValidationSalt, ownerKeySalt); // Algorithm 9b: Compute OE byte[] hashOE = ComputeHash2B(Concat(ownerPasswordBytes, ownerKeySalt, u), ownerPasswordBytes, u); byte[] oe = null; using (var enciptor = cipher.CreateEncryptor(hashOE, new byte[16]))// "an initialization vector of zero" { oe = enciptor.DoFinal(encryptionKey); } // Set keys and other required constants in encryption dictionary encryptionDictionary.UserKey = u; encryptionDictionary.UserEncryptionKey = ue; encryptionDictionary.OwnerKey = o; encryptionDictionary.OwnerEncryptionKey = oe; PrepareEncryptionDictAES(encryptionDictionary, PdfName.AESV3); // Algorithm 10: compute "Perms" value byte[] perms = new byte[16]; perms[0] = (byte)permissionInt; perms[1] = (byte)((uint)permissionInt >> 8); perms[2] = (byte)((uint)permissionInt >> 16); perms[3] = (byte)((uint)permissionInt >> 24); perms[4] = (byte)0xFF; perms[5] = (byte)0xFF; perms[6] = (byte)0xFF; perms[7] = (byte)0xFF; perms[8] = (byte)'T'; // we always encrypt Metadata perms[9] = (byte)'a'; perms[10] = (byte)'d'; perms[11] = (byte)'b'; for (int i = 12; i <= 15; i++) { perms[i] = (byte)rnd.NextInt(); } byte[] permsEnc = null; using (var enciptor = cipher.CreateEncryptor(encryptionKey, new byte[16])) // "an initialization vector of zero" { permsEnc = enciptor.DoFinal(perms); } encryptionDictionary.Perms = permsEnc; } } catch (Exception e) { LogIfStrongEncryptionMissing(); throw new IOException("PrepareEncryptionDictRev6", e); } }
/** * Prepares everything to decrypt the document. * * Only if decryption of single objects is needed this should be called. * * @param encryption encryption dictionary * @param documentIDArray document id * @param decryptionMaterial Information used to decrypt the document. * * @throws InvalidPasswordException If the password is incorrect. * @ If there is an error accessing data. */ public override void PrepareForDecryption(PdfEncryption encryption, PdfArray documentIDArray, DecryptionMaterial decryptionMaterial) { if (!(decryptionMaterial is StandardDecryptionMaterial)) { throw new IOException("Decryption material is not compatible with the document"); } // This is only used with security version 4 and 5. if (encryption.Version >= 4) { SetStreamFilterName(encryption.StreamFilterName); SetStringFilterName(encryption.StreamFilterName); } SetDecryptMetadata(encryption.IsEncryptMetaData); StandardDecryptionMaterial material = (StandardDecryptionMaterial)decryptionMaterial; string password = material.Password ?? string.Empty; int dicPermissions = encryption.Permissions; int dicRevision = encryption.Revision; int dicLength = encryption.Version == 1 ? 5 : encryption.Length / 8; byte[] documentIDBytes = GetDocumentIDBytes(documentIDArray); // we need to know whether the meta data was encrypted for password calculation bool encryptMetadata = encryption.IsEncryptMetaData; byte[] userKey = encryption.UserKey; byte[] ownerKey = encryption.OwnerKey; byte[] ue = null, oe = null; var passwordCharset = Charset.ISO88591; if (dicRevision == 6 || dicRevision == 5) { passwordCharset = Charset.UTF8; ue = encryption.UserEncryptionKey; oe = encryption.OwnerEncryptionKey; } if (dicRevision == 6) { password = SaslPrep.SaslPrepQuery(password); // PDFBOX-4155 } AccessPermission currentAccessPermission; if (IsOwnerPassword(passwordCharset.GetBytes(password), userKey, ownerKey, dicPermissions, documentIDBytes, dicRevision, dicLength, encryptMetadata)) { currentAccessPermission = AccessPermission.getOwnerAccessPermission(); CurrentAccessPermission = currentAccessPermission; byte[] computedPassword; if (dicRevision == 6 || dicRevision == 5) { computedPassword = passwordCharset.GetBytes(password); } else { computedPassword = GetUserPassword(passwordCharset.GetBytes(password), ownerKey, dicRevision, dicLength); } encryptionKey = ComputeEncryptedKey( computedPassword, ownerKey, userKey, oe, ue, dicPermissions, documentIDBytes, dicRevision, dicLength, encryptMetadata, true); } else if (IsUserPassword(passwordCharset.GetBytes(password), userKey, ownerKey, dicPermissions, documentIDBytes, dicRevision, dicLength, encryptMetadata)) { currentAccessPermission = new AccessPermission(dicPermissions); currentAccessPermission.IsReadOnly = true; CurrentAccessPermission = currentAccessPermission; encryptionKey = ComputeEncryptedKey( passwordCharset.GetBytes(password), ownerKey, userKey, oe, ue, dicPermissions, documentIDBytes, dicRevision, dicLength, encryptMetadata, false); } else { throw new InvalidPasswordException("Cannot decrypt PDF, the password is incorrect"); } if (dicRevision == 6 || dicRevision == 5) { ValidatePerms(encryption, dicPermissions, encryptMetadata); } if (encryption.Version == 4 || encryption.Version == 5) { // detect whether AES encryption is used. This assumes that the encryption algo is // stored in the PDCryptFilterDictionary // However, crypt filters are used only when V is 4 or 5. var stdCryptFilterDictionary = encryption.StdCryptFilterDictionary; if (stdCryptFilterDictionary != null) { PdfName cryptFilterMethod = stdCryptFilterDictionary.CryptFilterMethod; IsAES = PdfName.AESV2.Equals(cryptFilterMethod) || PdfName.AESV3.Equals(cryptFilterMethod); } } }
/** * Prepares everything to decrypt the document. * * @param encryption encryption dictionary, can be retrieved via {@link PDDocument#getEncryption()} * @param documentIDArray document id which is returned via {@link org.apache.pdfbox.cos.PdfDocument#getDocumentID()} * @param decryptionMaterial Information used to decrypt the document. * * @throws InvalidPasswordException If the password is incorrect. * @throws IOException If there is an error accessing data. */ public abstract void PrepareForDecryption(PdfEncryption encryption, PdfArray documentIDArray, DecryptionMaterial decryptionMaterial);