summaryrefslogtreecommitdiff
path: root/Build/source/libs/pplib/pplib-src/src/ppcrypt.c
diff options
context:
space:
mode:
Diffstat (limited to 'Build/source/libs/pplib/pplib-src/src/ppcrypt.c')
-rw-r--r--Build/source/libs/pplib/pplib-src/src/ppcrypt.c525
1 files changed, 525 insertions, 0 deletions
diff --git a/Build/source/libs/pplib/pplib-src/src/ppcrypt.c b/Build/source/libs/pplib/pplib-src/src/ppcrypt.c
new file mode 100644
index 00000000000..266bacc29b2
--- /dev/null
+++ b/Build/source/libs/pplib/pplib-src/src/ppcrypt.c
@@ -0,0 +1,525 @@
+
+#include "utilmd5.h"
+#include "utilsha.h"
+
+#include "pplib.h"
+
+/* crypt struct */
+
+static ppcrypt * ppcrypt_create (ppheap *heap)
+{
+ ppcrypt *crypt;
+ crypt = (ppcrypt *)ppstruct_take(heap, sizeof(ppcrypt));
+ memset(crypt, 0, sizeof(ppcrypt));
+ return crypt;
+}
+
+int ppcrypt_type (ppcrypt *crypt, ppname *cryptname, ppuint *length, int *cryptflags)
+{
+ ppdict *filterdict;
+ ppname *filtertype;
+ int cryptmd = 0, default256 = 0;
+
+ if (crypt->map == NULL || (filterdict = ppdict_rget_dict(crypt->map, cryptname->data)) == NULL)
+ return 0;
+ if ((filtertype = ppdict_get_name(filterdict, "CFM")) == NULL)
+ return 0;
+ *cryptflags = 0;
+ if (ppname_is(filtertype, "V2"))
+ *cryptflags |= PPCRYPT_INFO_RC4;
+ else if (ppname_is(filtertype, "AESV2"))
+ *cryptflags |= PPCRYPT_INFO_AES;
+ else if (ppname_is(filtertype, "AESV3"))
+ *cryptflags |= PPCRYPT_INFO_AES, default256 = 1;
+ else
+ return 0;
+ /* pdf spec page. 134: /Length is said to be optional bit-length of the key, but it seems to be a mistake, as Acrobat
+ produces /Length key with bytes lengths, opposite to /Length key of the main encrypt dict. */
+ if (length != NULL)
+ if (!ppdict_get_uint(filterdict, "Length", length))
+ *length = (*cryptflags & PPCRYPT_INFO_RC4) ? 5 : (default256 ? 32 : 16);
+ /* one of metadata flags is set iff there is an explicit EncryptMetadata key */
+ if (ppdict_get_bool(filterdict, "EncryptMetadata", &cryptmd))
+ *cryptflags |= (cryptmd ? PPCRYPT_INFO_MD : PPCRYPT_INFO_NOMD);
+ return 1;
+}
+
+static const uint8_t padding_string[] = {
+ 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
+};
+
+static void ppcrypt_set_userpass (ppcrypt *crypt, const void *userpass, size_t userpasslength)
+{
+ crypt->userpasslength = userpasslength > 32 ? 32 : userpasslength;
+ memcpy(crypt->userpass, userpass, crypt->userpasslength);
+ memcpy(crypt->userpass + crypt->userpasslength, padding_string, 32 - crypt->userpasslength);
+ crypt->flags |= PPCRYPT_USER_PASSWORD;
+}
+
+static void ppcrypt_set_ownerpass (ppcrypt *crypt, const void *ownerpass, size_t ownerpasslength)
+{
+ crypt->ownerpasslength = ownerpasslength > 32 ? 32 : ownerpasslength;
+ memcpy(crypt->ownerpass, ownerpass, crypt->ownerpasslength);
+ memcpy(crypt->ownerpass + crypt->ownerpasslength, padding_string, 32 - crypt->ownerpasslength);
+ crypt->flags |= PPCRYPT_OWNER_PASSWORD;
+}
+
+/* retrieving user password from owner password and owner key (variant < 5) */
+
+static void ppcrypt_retrieve_userpass (ppcrypt *crypt, const void *ownerkey, size_t ownerkeysize)
+{
+ uint8_t temp[16], rc4key[32], rc4key2[32];
+ uint8_t i;
+ ppuint k;
+ md5_state md5;
+
+ md5_digest_init(&md5);
+ md5_digest_add(&md5, crypt->ownerpass, 32);
+ md5_digest_get(&md5, rc4key, MD5_BYTES);
+ if (crypt->algorithm_revision >= 3)
+ {
+ for (i = 0; i < 50; ++i)
+ {
+ md5_digest(rc4key, 16, temp, MD5_BYTES);
+ memcpy(rc4key, temp, 16);
+ }
+ }
+ rc4_decode_data(ownerkey, ownerkeysize, crypt->userpass, rc4key, crypt->filekeylength);
+ if (crypt->algorithm_revision >= 3)
+ {
+ for (i = 1; i <= 19; ++i)
+ {
+ for (k = 0; k < crypt->filekeylength; ++k)
+ rc4key2[k] = rc4key[k] ^ i;
+ rc4_decode_data(crypt->userpass, 32, crypt->userpass, rc4key2, crypt->filekeylength);
+ }
+ }
+ //crypt->userpasslength = 32;
+ for (crypt->userpasslength = 0; crypt->userpasslength < 32; ++crypt->userpasslength)
+ if (memcmp(&crypt->userpass[crypt->userpasslength], padding_string, 32 - crypt->userpasslength) == 0)
+ break;
+ crypt->flags |= PPCRYPT_USER_PASSWORD;
+}
+
+/* generating file key; pdf spec p. 125 */
+
+static void ppcrypt_filekey (ppcrypt *crypt, const void *ownerkey, size_t ownerkeysize, const void *id, size_t idsize)
+{
+ uint32_t p;
+ uint8_t permissions[4], temp[16];
+ int i;
+ md5_state md5;
+
+ md5_digest_init(&md5);
+ md5_digest_add(&md5, crypt->userpass, 32);
+ md5_digest_add(&md5, ownerkey, ownerkeysize);
+ p = (uint32_t)crypt->permissions;
+ permissions[0] = get_number_byte1(p);
+ permissions[1] = get_number_byte2(p);
+ permissions[2] = get_number_byte3(p);
+ permissions[3] = get_number_byte4(p);
+ md5_digest_add(&md5, permissions, 4);
+ md5_digest_add(&md5, id, idsize);
+ if (crypt->algorithm_revision >= 4 && (crypt->flags & PPCRYPT_NO_METADATA))
+ md5_digest_add(&md5, "\xFF\xFF\xFF\xFF", 4);
+ md5_digest_get(&md5, crypt->filekey, MD5_BYTES);
+ if (crypt->algorithm_revision >= 3)
+ {
+ for (i = 0; i < 50; ++i)
+ {
+ md5_digest(crypt->filekey, (size_t)crypt->filekeylength, temp, MD5_BYTES);
+ memcpy(crypt->filekey, temp, 16);
+ }
+ }
+}
+
+/* generating userkey for comparison with /U; requires a general file key and id; pdf spec page 126-127 */
+
+static void ppcrypt_userkey (ppcrypt *crypt, const void *id, size_t idsize, uint8_t *password_hash)
+{
+ uint8_t rc4key2[32];
+ uint8_t i;
+ ppuint k;
+
+ if (crypt->algorithm_revision <= 2)
+ {
+ rc4_encode_data(padding_string, 32, password_hash, crypt->filekey, crypt->filekeylength);
+ }
+ else
+ {
+ md5_state md5;
+ md5_digest_init(&md5);
+ md5_digest_add(&md5, padding_string, 32);
+ md5_digest_add(&md5, id, idsize);
+ md5_digest_get(&md5, password_hash, MD5_BYTES);
+ rc4_encode_data(password_hash, 16, password_hash, crypt->filekey, crypt->filekeylength);
+ for (i = 1; i <= 19; ++i)
+ {
+ for (k = 0; k < crypt->filekeylength; ++k)
+ rc4key2[k] = crypt->filekey[k] ^ i;
+ rc4_encode_data(password_hash, 16, password_hash, rc4key2, crypt->filekeylength);
+ }
+ for (i = 16; i < 32; ++i)
+ password_hash[i] = password_hash[i - 16] ^ i; /* arbitrary 16-bytes padding */
+ }
+}
+
+/* validating /Perms key (pdf 1.7, /V 5 /R 5 crypt) */
+
+static const uint8_t nulliv[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; /* AES-256 initialization vector */
+
+static ppcrypt_status ppcrypt_authenticate_perms (ppcrypt *crypt, ppstring *perms)
+{ /* decode /Perms string overriding crypt setup (should match anyway) */
+ uint8_t permsdata[16];
+ //int64_t p;
+ //int i;
+
+ aes_decode_data(perms->data, perms->size, permsdata, crypt->filekey, crypt->filekeylength, nulliv, AES_NULL_PADDING);
+
+ if (permsdata[9] != 'a' || permsdata[10] != 'd' || permsdata[11] != 'b')
+ return PPCRYPT_FAIL;
+
+ // do not update permissions flags; they seem to be different inside crypt string
+ //for (p = 0, i = 0; i < 8; ++i)
+ // p = p + (permsdata[i] << (i << 3)); /* low order bytes first */
+ //crypt->permissions = (ppint)(int32_t)(p & 0x00000000FFFFFFFFLL); /* unset bits 33..64, treat as 32-bit signed int */
+
+ if (permsdata[8] == 'T')
+ crypt->flags &= ~PPCRYPT_NO_METADATA;
+ else if (permsdata[8] == 'F')
+ crypt->flags |= PPCRYPT_NO_METADATA;
+
+ return PPCRYPT_DONE;
+}
+
+ppcrypt_status ppdoc_crypt_init (ppdoc *pdf, const void *userpass, size_t userpasslength, const void *ownerpass, size_t ownerpasslength)
+{
+ ppcrypt *crypt;
+ ppdict *trailer, *encrypt;
+ ppobj *obj;
+ ppname *name, **pkey;
+ ppstring *userkey, *ownerkey, *userkey_e = NULL, *ownerkey_e = NULL;
+ size_t hashlength;
+ pparray *idarray;
+ ppstring *id = NULL, *perms = NULL;
+ int cryptflags, encryptmd;
+ size_t strkeylength, stmkeylength;
+
+ uint8_t password_hash[32]; /* /U and /O are 48 bytes strings for AES-256, but here we use only 32 */
+ uint8_t *validation_salt, *key_salt;
+
+ /* Every xref could theoretically have a separate encryption info. Not clarified in pdf spec but it seems that the top
+ level xref encryption info is the one to be applied to all objects in all xrefs, including older. */
+ trailer = ppxref_trailer(pdf->xref);
+ if ((obj = ppdict_get_obj(trailer, "Encrypt")) == NULL)
+ return PPCRYPT_NONE;
+ /* Typically this is all done early, before loading body, so if /Encrypt is indirect reference, it points nothing. We have to load it here. */
+ obj = ppobj_preloaded(pdf, obj);
+ if (obj->type != PPDICT)
+ return PPCRYPT_FAIL;
+ encrypt = obj->dict;
+ for (ppdict_first(encrypt, pkey, obj); *pkey != NULL; ppdict_next(pkey, obj))
+ (void)ppobj_preloaded(pdf, obj);
+
+ if ((name = ppdict_get_name(encrypt, "Filter")) != NULL && !ppname_is(name, "Standard"))
+ return PPCRYPT_FAIL;
+
+ if ((crypt = pdf->crypt) == NULL)
+ crypt = pdf->crypt = ppcrypt_create(&pdf->heap);
+ if (!ppdict_get_uint(encrypt, "V", &crypt->algorithm_variant))
+ crypt->algorithm_variant = 0;
+ if (crypt->algorithm_variant < 1 || crypt->algorithm_variant > 5)
+ return PPCRYPT_FAIL;
+ if (!ppdict_get_uint(encrypt, "R", &crypt->algorithm_revision))
+ return PPCRYPT_FAIL;
+ if (crypt->algorithm_revision >= 3)
+ crypt->flags |= PPCRYPT_OBSCURITY;
+ if (!ppdict_get_int(encrypt, "P", &crypt->permissions))
+ return PPCRYPT_FAIL;
+ if ((userkey = ppdict_get_string(encrypt, "U")) == NULL || (ownerkey = ppdict_get_string(encrypt, "O")) == NULL)
+ return PPCRYPT_FAIL;
+ userkey = ppstring_decoded(userkey);
+ ownerkey = ppstring_decoded(ownerkey);
+ /* for some reason acrobat pads /O and /U to 127 bytes with NULL, so we don't check the exact length but ensure the minimal */
+ hashlength = crypt->algorithm_variant < 5 ? 32 : 48;
+ if (userkey->size < hashlength || ownerkey->size < hashlength)
+ return PPCRYPT_FAIL;
+ if (crypt->algorithm_variant < 5)
+ { // get first string from /ID (must not be ref)
+ if ((idarray = ppdict_get_array(trailer, "ID")) == NULL || (id = pparray_get_string(idarray, 0)) == NULL)
+ return PPCRYPT_FAIL;
+ id = ppstring_decoded(id);
+ }
+ else
+ {
+ if ((userkey_e = ppdict_get_string(encrypt, "UE")) == NULL || (ownerkey_e = ppdict_get_string(encrypt, "OE")) == NULL)
+ return PPCRYPT_FAIL;
+ userkey_e = ppstring_decoded(userkey_e);
+ ownerkey_e = ppstring_decoded(ownerkey_e);
+ if (userkey_e->size < 32 || ownerkey_e->size < 32)
+ return PPCRYPT_FAIL;
+ if ((perms = ppdict_get_string(encrypt, "Perms")) == NULL)
+ return PPCRYPT_FAIL;
+ perms = ppstring_decoded(perms);
+ if (perms->size != 16)
+ return PPCRYPT_FAIL;
+ }
+
+ switch (crypt->algorithm_revision)
+ {
+ case 1:
+ crypt->filekeylength = 5;
+ crypt->flags |= PPCRYPT_RC4;
+ break;
+ case 2: case 3:
+ if (ppdict_get_uint(encrypt, "Length", &crypt->filekeylength))
+ crypt->filekeylength >>= 3; /* 40..256 bits, 5..32 bytes*/
+ else
+ crypt->filekeylength = 5; /* 40 bits, 5 bytes */
+ crypt->flags |= PPCRYPT_RC4;
+ break;
+ case 4: case 5:
+ if ((crypt->map = ppdict_rget_dict(encrypt, "CF")) == NULL)
+ return PPCRYPT_FAIL;
+ for (ppdict_first(crypt->map, pkey, obj); *pkey != NULL; ppdict_next(pkey, obj))
+ (void)ppobj_preloaded(pdf, obj);
+ /* /EncryptMetadata relevant only for version >=4, may be also provided in crypt filter dictionary; which takes a precedence then?
+ we assume that if there is an explicit EncryptMetadata key, it overrides main encrypt dict flag or default flag (the default is true,
+ meaning that Metadata stream is encrypted as others) */
+ if (ppdict_get_bool(encrypt, "EncryptMetadata", &encryptmd) && !encryptmd)
+ crypt->flags |= PPCRYPT_NO_METADATA;
+
+ strkeylength = stmkeylength = 0;
+ /* streams filter */
+ if ((name = ppdict_get_name(encrypt, "StmF")) != NULL && ppcrypt_type(crypt, name, &stmkeylength, &cryptflags))
+ {
+ if (cryptflags & PPCRYPT_INFO_AES)
+ crypt->flags |= PPCRYPT_STREAM_AES;
+ else if (cryptflags & PPCRYPT_INFO_RC4)
+ crypt->flags |= PPCRYPT_STREAM_RC4;
+ if (cryptflags & PPCRYPT_INFO_NOMD)
+ crypt->flags |= PPCRYPT_NO_METADATA;
+ else if (cryptflags & PPCRYPT_INFO_MD)
+ crypt->flags &= ~PPCRYPT_NO_METADATA;
+ } /* else identity */
+ /* strings filter */
+ if ((name = ppdict_get_name(encrypt, "StrF")) != NULL && ppcrypt_type(crypt, name, &strkeylength, &cryptflags))
+ {
+ if (cryptflags & PPCRYPT_INFO_AES)
+ crypt->flags |= PPCRYPT_STRING_AES;
+ else if (cryptflags & PPCRYPT_INFO_RC4)
+ crypt->flags |= PPCRYPT_STRING_RC4;
+ } /* else identity */
+
+ /* /Length of encrypt dict is irrelevant here, theoretically every crypt filter may have own length... It means that we should
+ actually keep a different file key for streams and strings. But it leads to nonsense, as /U and /O entries refers to a single
+ keylength, without a distinction for strings/streams. So we have to assume /Length is consistent. To expose the limitation: */
+ if ((crypt->flags & PPCRYPT_STREAM) && (crypt->flags & PPCRYPT_STRING))
+ if (strkeylength != stmkeylength)
+ return PPCRYPT_FAIL;
+ crypt->filekeylength = stmkeylength ? stmkeylength : strkeylength;
+ if ((crypt->flags & PPCRYPT_STREAM) || (crypt->flags & PPCRYPT_STRING))
+ if (crypt->filekeylength == 0)
+ return PPCRYPT_FAIL;
+ break;
+ default:
+ return PPCRYPT_FAIL;
+ }
+
+ /* password */
+
+ if (userpass != NULL)
+ {
+ ppcrypt_set_userpass(crypt, userpass, userpasslength);
+ }
+ else if (ownerpass != NULL)
+ {
+ if (crypt->algorithm_variant < 5) // fetch user password from owner password
+ ppcrypt_retrieve_userpass(crypt, ownerkey, ppstring_size(ownerkey));
+ else // open the document using owner password
+ ppcrypt_set_ownerpass(crypt, ownerpass, ownerpasslength);
+ }
+ else
+ {
+ return PPCRYPT_FAIL;
+ }
+
+ if (crypt->algorithm_variant < 5)
+ { /* authenticate by comparing a generated vs present /U entry; depending on variant 16 or 32 bytes to compare */
+ ppcrypt_filekey(crypt, ownerkey->data, ownerkey->size, id->data, id->size);
+ ppcrypt_userkey(crypt, id->data, id->size, password_hash); /* needs file key so comes after key generation */
+ if (memcmp(userkey->data, password_hash, (crypt->algorithm_revision >= 3 ? 16 : 32)) == 0)
+ return PPCRYPT_DONE;
+ return PPCRYPT_PASS;
+ }
+ if (crypt->flags & PPCRYPT_USER_PASSWORD)
+ {
+ sha256_state sha;
+ validation_salt = (uint8_t *)userkey + 32;
+ key_salt = validation_salt + 8;
+ sha256_digest_init(&sha);
+ sha256_digest_add(&sha, crypt->userpass, crypt->userpasslength);
+ sha256_digest_add(&sha, validation_salt, 8);
+ sha256_digest_get(&sha, password_hash, SHA_BYTES);
+ if (memcmp(userkey->data, password_hash, 32) != 0)
+ return PPCRYPT_PASS;
+ sha256_digest_init(&sha);
+ sha256_digest_add(&sha, crypt->userpass, crypt->userpasslength);
+ sha256_digest_add(&sha, key_salt, 8);
+ sha256_digest_get(&sha, password_hash, SHA_BYTES);
+ aes_decode_data(userkey_e->data, 32, crypt->filekey, password_hash, 32, nulliv, AES_NULL_PADDING);
+ return ppcrypt_authenticate_perms(crypt, perms);
+ }
+ if (crypt->flags & PPCRYPT_OWNER_PASSWORD)
+ {
+ sha256_state sha;
+ validation_salt = (uint8_t *)ownerkey + 32;
+ key_salt = validation_salt + 8;
+
+ sha256_digest_init(&sha);
+ sha256_digest_add(&sha, crypt->ownerpass, crypt->ownerpasslength);
+ sha256_digest_add(&sha, validation_salt, 8);
+ sha256_digest_add(&sha, userkey, 48);
+ sha256_digest_get(&sha, password_hash, SHA_BYTES);
+ if (memcmp(ownerkey->data, password_hash, 32) != 0)
+ return PPCRYPT_PASS;
+ sha256_digest_init(&sha);
+ sha256_digest_add(&sha, crypt->ownerpass, crypt->ownerpasslength);
+ sha256_digest_add(&sha, key_salt, 8);
+ sha256_digest_add(&sha, userkey, 48);
+ sha256_digest_get(&sha, password_hash, SHA_BYTES);
+ aes_decode_data(ownerkey_e->data, 32, crypt->filekey, password_hash, 32, nulliv, AES_NULL_PADDING);
+ return ppcrypt_authenticate_perms(crypt, perms);
+ }
+ return PPCRYPT_FAIL; // should never get here
+}
+
+/* decrypting strings */
+
+/*
+Since strings are generally rare, but might occur in mass (name trees). We generate decryption key when needed.
+All strings within the same reference are crypted with the same key. Both RC4 and AES algorithms expands
+the crypt key in some way and the result of expansion is the same for the same crypt key. Instead of recreating
+the ky for every string, we backup the initial decryption state.
+*/
+
+static void ppcrypt_strkey (ppcrypt *crypt, ppref *ref, int aes)
+{
+ if (crypt->cryptkeylength > 0)
+ { /* crypt key already generated, just reinitialize crypt states */
+ if (aes)
+ { /* aes codecs that works on c-strings do not modify aes_state flags at all, so we actually don't need to revitalize the state,
+ we only rewrite an initialization vector, which is modified during crypt procedure */
+ }
+ else
+ { /* rc4 crypt map is modified during crypt procedure, so here we reinitialize rc4 bytes map */
+ rc4_map_restore(&crypt->rc4state, &crypt->rc4copy);
+ }
+ return;
+ }
+
+ if (crypt->algorithm_variant < 5)
+ {
+ crypt->filekey[crypt->filekeylength + 0] = get_number_byte1(ref->number);
+ crypt->filekey[crypt->filekeylength + 1] = get_number_byte2(ref->number);
+ crypt->filekey[crypt->filekeylength + 2] = get_number_byte3(ref->number);
+ crypt->filekey[crypt->filekeylength + 3] = get_number_byte1(ref->version);
+ crypt->filekey[crypt->filekeylength + 4] = get_number_byte2(ref->version);
+
+ if (aes)
+ {
+ crypt->filekey[crypt->filekeylength + 5] = 0x73;
+ crypt->filekey[crypt->filekeylength + 6] = 0x41;
+ crypt->filekey[crypt->filekeylength + 7] = 0x6C;
+ crypt->filekey[crypt->filekeylength + 8] = 0x54;
+ }
+
+ md5_digest(crypt->filekey, crypt->filekeylength + (aes ? 9 : 5), crypt->cryptkey, MD5_BYTES);
+ crypt->cryptkeylength = crypt->filekeylength + 5 >= 16 ? 16 : crypt->filekeylength + 5;
+ }
+ else
+ {
+ memcpy(crypt->cryptkey, crypt->filekey, 32);
+ crypt->cryptkeylength = 32;
+ }
+
+ if (aes)
+ {
+ aes_decode_initialize(&crypt->aesstate, &crypt->aeskeyblock, crypt->cryptkey, crypt->cryptkeylength, NULL);
+ aes_pdf_mode(&crypt->aesstate);
+ }
+ else
+ {
+ rc4_state_initialize(&crypt->rc4state, &crypt->rc4map, crypt->cryptkey, crypt->cryptkeylength);
+ rc4_map_save(&crypt->rc4state, &crypt->rc4copy);
+ }
+}
+
+int ppstring_decrypt (ppcrypt *crypt, const void *input, size_t size, void *output, size_t *newsize)
+{
+ int aes, rc4;
+ aes = crypt->flags & PPCRYPT_STRING_AES;
+ rc4 = crypt->flags & PPCRYPT_STRING_RC4;
+ if (aes || rc4)
+ {
+ ppcrypt_strkey(crypt, crypt->ref, aes);
+ if (aes)
+ *newsize = aes_decode_state_data(&crypt->aesstate, input, size, output);
+ else // if (rc4)
+ *newsize = rc4_decode_state_data(&crypt->rc4state, input, size, output);
+ return 1;
+ }
+ return 0; // identity crypt
+}
+
+/* decrypting streams */
+
+/*
+Streams are decrypted everytime when accessing the stream data. We need to be able to get or make
+the key for decryption as long as the stream is alive. And to get the key we need the reference
+number and version, plus document crypt info. First thought was to keep the reference to which
+the stream belongs; stream->ref and accessing the crypt info stream->ref->xref->pdf->crypt.
+It would be ok as long as absolutelly nothing happens with ref and crypt. At some point pplib
+may drift into rewriting support, which would imply ref/xref/crypt/pdf structures modifications.
+So I feel better with generating a crypt key for every stream in encrypted document, paying a cost
+of md5 for all streams, not necessarily those actually read.
+
+Key generation is the same as for strings, but different for distinct encryption methods (rc4 vs aes).
+Since streams and strings might theoretically be encrypted with different filters. No reason to cacche
+decryption state here.
+*/
+
+ppstring * ppcrypt_stmkey (ppcrypt *crypt, ppref *ref, int aes, ppheap *heap)
+{
+ ppstring *cryptkeystring;
+ //if (crypt->cryptkeylength > 0)
+ // return;
+
+ if (crypt->algorithm_variant < 5)
+ {
+ crypt->filekey[crypt->filekeylength + 0] = get_number_byte1(ref->number);
+ crypt->filekey[crypt->filekeylength + 1] = get_number_byte2(ref->number);
+ crypt->filekey[crypt->filekeylength + 2] = get_number_byte3(ref->number);
+ crypt->filekey[crypt->filekeylength + 3] = get_number_byte1(ref->version);
+ crypt->filekey[crypt->filekeylength + 4] = get_number_byte2(ref->version);
+
+ if (aes)
+ {
+ crypt->filekey[crypt->filekeylength + 5] = 0x73;
+ crypt->filekey[crypt->filekeylength + 6] = 0x41;
+ crypt->filekey[crypt->filekeylength + 7] = 0x6C;
+ crypt->filekey[crypt->filekeylength + 8] = 0x54;
+ }
+
+ md5_digest(crypt->filekey, crypt->filekeylength + (aes ? 9 : 5), crypt->cryptkey, MD5_BYTES);
+ crypt->cryptkeylength = crypt->filekeylength + 5 >= 16 ? 16 : crypt->filekeylength + 5; // how about 256bits AES??
+ }
+ else
+ { // we could actually generate this string once, but.. aes itself is way more expensive that we can earn here
+ memcpy(crypt->cryptkey, crypt->filekey, 32); // just for the record
+ crypt->cryptkeylength = 32;
+ }
+ cryptkeystring = ppstring_internal(crypt->cryptkey, crypt->cryptkeylength, heap);
+ return ppstring_decoded(cryptkeystring);
+}