From c4bad2186fcfe24ca47eb92c80ac2c07d0d11575 Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Mon, 17 Jul 2023 20:39:55 -0400 Subject: [PATCH] LibPDF: Implement 7.6.4.3.4 Algorithm 2.B: Computing a hash This is a step towards AESV3 support for PDF files. The straight-forward way of writing this with our APIs is pretty allocation-heavy, but this code won't run all that often for the regular "open PDF, check password" flow. --- Userland/Libraries/LibPDF/Encryption.cpp | 95 ++++++++++++++++++------ Userland/Libraries/LibPDF/Encryption.h | 7 +- 2 files changed, 80 insertions(+), 22 deletions(-) diff --git a/Userland/Libraries/LibPDF/Encryption.cpp b/Userland/Libraries/LibPDF/Encryption.cpp index bace06fb93..e66fbe1605 100644 --- a/Userland/Libraries/LibPDF/Encryption.cpp +++ b/Userland/Libraries/LibPDF/Encryption.cpp @@ -7,7 +7,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -443,43 +445,94 @@ ByteBuffer StandardSecurityHandler::compute_encryption_key_r6_and_later(ByteBuff TODO(); } -ByteBuffer StandardSecurityHandler::computing_a_hash_r6_and_later(ByteBuffer) +ByteBuffer StandardSecurityHandler::computing_a_hash_r6_and_later(ByteBuffer original_input, StringView input_password, HashKind kind) { // ISO 32000 (PDF 2.0), 7.6.4.3.4 Algorithm 2.B: Computing a hash (revision 6 or later) // Take the SHA-256 hash of the original input to the algorithm and name the resulting 32 bytes, K. + static_assert(Crypto::Hash::SHA256::DigestType::Size == 32); + Crypto::Hash::SHA256 sha; + sha.update(original_input); + auto K = MUST(ByteBuffer::copy(sha.digest().bytes())); + // Perform the following steps (a)-(d) 64 times: + int round_number; + for (round_number = 0;; ++round_number) { + // a) Make a new string, K1, consisting of 64 repetitions of the sequence: Input password, K, the 48-byte user + // key. The 48 byte user key is only used when checking the owner password or creating the owner key. If + // checking the user password or creating the user key, K1 is the concatenation of the input password and K. + ByteBuffer K1_part; + K1_part.append(input_password.bytes()); + K1_part.append(K.bytes()); + if (kind == HashKind::Owner) + K1_part.append(m_u_entry.bytes()); - // a) Make a new string, K1, consisting of 64 repetitions of the sequence: Input password, K, the 48-byte user - // key. The 48 byte user key is only used when checking the owner password or creating the owner key. If - // checking the user password or creating the user key, K1 is the concatenation of the input password and K. + ByteBuffer K1; + for (int i = 0; i < 64; ++i) + K1.append(K1_part); - // b) Encrypt K1 with the AES-128 (CBC, no padding) algorithm, using the first 16 bytes of K as the key and - // the second 16 bytes of K as the initialization vector. The result of this encryption is E. + // b) Encrypt K1 with the AES-128 (CBC, no padding) algorithm, using the first 16 bytes of K as the key and + // the second 16 bytes of K as the initialization vector. The result of this encryption is E. + ReadonlyBytes key = K.bytes().trim(16); + ReadonlyBytes initialization_vector = K.bytes().slice(16); - // c) Taking the first 16 bytes of E as an unsigned big-endian integer, compute the remainder, modulo 3. If the - // result is 0, the next hash used is SHA-256, if the result is 1, the next hash used is SHA-384, if the result is - // 2, the next hash used is SHA-512. + // (PaddingMode doesn't matter here since input is block-aligned.) + auto cipher = Crypto::Cipher::AESCipher::CBCMode(key, 128, Crypto::Cipher::Intent::Encryption, Crypto::Cipher::PaddingMode::Null); + auto E = MUST(cipher.create_aligned_buffer(K1.size())); + Bytes E_span = E.bytes(); + cipher.encrypt(K1, E_span, initialization_vector); - // d) Using the hash algorithm determined in step c, take the hash of E. The result is a new value of K, which - // will be 32, 48, or 64 bytes in length. + // c) Taking the first 16 bytes of E as an unsigned big-endian integer, compute the remainder, modulo 3. If the + // result is 0, the next hash used is SHA-256, if the result is 1, the next hash used is SHA-384, if the result is + // 2, the next hash used is SHA-512. + u128 remainder(0); + for (int i = 0; i < 16; ++i) + remainder = (remainder << 8) | E[i]; + remainder %= u128(3); - // Repeat the process (a-d) with this new value of K. Following 64 rounds (round number 0 to round - // number 63), do the following, starting with round number 64: + Crypto::Hash::HashKind hash_kind; + switch (u8 { remainder }) { + case 0: + hash_kind = Crypto::Hash::HashKind::SHA256; + break; + case 1: + hash_kind = Crypto::Hash::HashKind::SHA384; + break; + case 2: + hash_kind = Crypto::Hash::HashKind::SHA512; + break; + } - // NOTE 2 The reason for multiple rounds is to defeat the possibility of running all paths in parallel. With 64 - // rounds (minimum) there are 3^64 paths through the algorithm. + // d) Using the hash algorithm determined in step c, take the hash of E. The result is a new value of K, which + // will be 32, 48, or 64 bytes in length. + Crypto::Hash::Manager hash(hash_kind); + hash.update(E); + K = MUST(ByteBuffer::copy(hash.digest().bytes())); - // e) Look at the very last byte of E. If the value of that byte (taken as an unsigned integer) is greater than the - // round number - 32, repeat steps (a-d) again. + // Repeat the process (a-d) with this new value of K. Following 64 rounds (round number 0 to round + // number 63), do the following, starting with round number 64: - // f) Repeat from steps (a-e) until the value of the last byte is <= (round number) - 32. + if (round_number < 64) + continue; - // NOTE 3 Tests indicate that the total number of rounds will most likely be between 65 and 80. + // NOTE 2 The reason for multiple rounds is to defeat the possibility of running all paths in parallel. With 64 + // rounds (minimum) there are 3^64 paths through the algorithm. + + // e) Look at the very last byte of E. If the value of that byte (taken as an unsigned integer) is greater than the + // round number - 32, repeat steps (a-d) again. + + // f) Repeat from steps (a-e) until the value of the last byte is <= (round number) - 32. + + // NOTE 3 Tests indicate that the total number of rounds will most likely be between 65 and 80. + + if (E.bytes().last() <= round_number - 32) + break; + } // The first 32 bytes of the final K are the output of the algorithm. - - TODO(); + VERIFY(K.size() >= 32); + K.resize(32); + return K; } void StandardSecurityHandler::crypt(NonnullRefPtr object, Reference reference, Crypto::Cipher::Intent direction) const diff --git a/Userland/Libraries/LibPDF/Encryption.h b/Userland/Libraries/LibPDF/Encryption.h index 6064214138..dd7491aa26 100644 --- a/Userland/Libraries/LibPDF/Encryption.h +++ b/Userland/Libraries/LibPDF/Encryption.h @@ -62,7 +62,12 @@ private: ByteBuffer compute_encryption_key_r2_to_r5(ByteBuffer password_string); ByteBuffer compute_encryption_key_r6_and_later(ByteBuffer password_string); - ByteBuffer computing_a_hash_r6_and_later(ByteBuffer password_string); + + enum class HashKind { + Owner, + User, + }; + ByteBuffer computing_a_hash_r6_and_later(ByteBuffer input, StringView input_password, HashKind); Document* m_document; size_t m_revision;