RSA public key encryption openssl - c++

a question: Vendor says that for some encryption purpose uses PKCS#1 V2.1 OAEP with SHA-256... Is that even possible?
I have checked and re-checked openssl and all they have is RSA public key encrypt with OAEP padding which is supposed to be PKCS#1 V2.1 with SHA1
So what can I do? How can I use SHA256 in RSA PUBLIC KEY encryption?
IS it even possible?
Best regards,
EDITED: ANSWER HOW TO USE RSA ENCRYPTION USING OPENSSL OAEP PADDING AND SHA256 DIGEST
#include "openssl/rsa.h"
#include <openssl/err.h>
#define RSA_F_RSA_PADDING_ADD_PKCS1_OAEP_MGF1 154
int RSA_padding_add_PKCS1_OAEP_mgf1_SHA256(unsigned char *to, int tlen,
const unsigned char *from, int flen,
const unsigned char *param, int plen,
const EVP_MD *md, const EVP_MD *mgf1md)
{
int i, emlen = tlen - 1;
unsigned char *db, *seed;
unsigned char *dbmask, seedmask[EVP_MAX_MD_SIZE];
int mdlen;
if (md == NULL)
md = EVP_sha256(); //HERE IS THE ACTUAL USE OF SHAR256 digest!
if (mgf1md == NULL)
mgf1md = md;
mdlen = EVP_MD_size(md);
if (flen > emlen - 2 * mdlen - 1)
{
RSAerr(RSA_F_RSA_PADDING_ADD_PKCS1_OAEP_MGF1,
RSA_R_DATA_TOO_LARGE_FOR_KEY_SIZE);
return 0;
}
if (emlen < 2 * mdlen + 1)
{
RSAerr(RSA_F_RSA_PADDING_ADD_PKCS1_OAEP_MGF1, RSA_R_KEY_SIZE_TOO_SMALL);
return 0;
}
to[0] = 0;
seed = to + 1;
db = to + mdlen + 1;
if (!EVP_Digest((void *)param, plen, db, NULL, md, NULL))
return 0;
memset(db + mdlen, 0,
emlen - flen - 2 * mdlen - 1);
db[emlen - flen - mdlen - 1] = 0x01;
memcpy(db + emlen - flen - mdlen, from, (unsigned int)flen);
if (RAND_bytes(seed, mdlen) <= 0)
return 0;
#ifdef PKCS_TESTVECT
memcpy(seed,
"\xaa\xfd\x12\xf6\x59\xca\xe6\x34\x89\xb4\x79\xe5\x07\x6d\xde\xc2\xf0\x6c\xb5\x8f",
20);
#endif
dbmask = (unsigned char*)OPENSSL_malloc(emlen - mdlen);
if (dbmask == NULL)
{
RSAerr(RSA_F_RSA_PADDING_ADD_PKCS1_OAEP_MGF1, ERR_R_MALLOC_FAILURE);
return 0;
}
if (PKCS1_MGF1(dbmask, emlen - mdlen, seed, mdlen, mgf1md) < 0)
return 0;
for (i = 0; i < emlen - mdlen; i++)
db[i] ^= dbmask[i];
if (PKCS1_MGF1(seedmask, mdlen, db, emlen - mdlen, mgf1md) < 0)
return 0;
for (i = 0; i < mdlen; i++)
seed[i] ^= seedmask[i];
OPENSSL_free(dbmask);
return 1;
}
int RSA_padding_add_PKCS1_OAEP_SHA256(unsigned char *to, int tlen,
const unsigned char *from, int flen,
const unsigned char *param, int plen)
{
return RSA_padding_add_PKCS1_OAEP_mgf1_SHA256(to, tlen, from, flen,
param, plen, NULL, NULL);
}
static int RSA_eay_public_encrypt_SHA256(int flen, const unsigned char *from,
unsigned char *to, RSA *rsa, int padding)
{
BIGNUM *f, *ret;
int i, j, k, num = 0, r = -1;
unsigned char *buf = NULL;
BN_CTX *ctx = NULL;
#ifdef OPENSSL_FIPS
if (FIPS_selftest_failed())
{
FIPSerr(FIPS_F_RSA_EAY_PUBLIC_ENCRYPT, FIPS_R_FIPS_SELFTEST_FAILED);
goto err;
}
if (FIPS_module_mode() && !(rsa->flags & RSA_FLAG_NON_FIPS_ALLOW)
&& (BN_num_bits(rsa->n) < OPENSSL_RSA_FIPS_MIN_MODULUS_BITS))
{
RSAerr(RSA_F_RSA_EAY_PUBLIC_ENCRYPT, RSA_R_KEY_SIZE_TOO_SMALL);
return -1;
}
#endif
if (BN_num_bits(rsa->n) > OPENSSL_RSA_MAX_MODULUS_BITS)
{
RSAerr(RSA_F_RSA_EAY_PUBLIC_ENCRYPT, RSA_R_MODULUS_TOO_LARGE);
return -1;
}
if (BN_ucmp(rsa->n, rsa->e) <= 0)
{
RSAerr(RSA_F_RSA_EAY_PUBLIC_ENCRYPT, RSA_R_BAD_E_VALUE);
return -1;
}
/* for large moduli, enforce exponent limit */
if (BN_num_bits(rsa->n) > OPENSSL_RSA_SMALL_MODULUS_BITS)
{
if (BN_num_bits(rsa->e) > OPENSSL_RSA_MAX_PUBEXP_BITS)
{
RSAerr(RSA_F_RSA_EAY_PUBLIC_ENCRYPT, RSA_R_BAD_E_VALUE);
return -1;
}
}
if ((ctx = BN_CTX_new()) == NULL) goto err;
BN_CTX_start(ctx);
f = BN_CTX_get(ctx);
ret = BN_CTX_get(ctx);
num = BN_num_bytes(rsa->n);
buf = (unsigned char*)OPENSSL_malloc(num);
if (!f || !ret || !buf)
{
RSAerr(RSA_F_RSA_EAY_PUBLIC_ENCRYPT, ERR_R_MALLOC_FAILURE);
goto err;
}
switch (padding)
{
case RSA_PKCS1_PADDING:
i = RSA_padding_add_PKCS1_type_2(buf, num, from, flen);
break;
#ifndef OPENSSL_NO_SHA
case RSA_PKCS1_OAEP_PADDING:
i = RSA_padding_add_PKCS1_OAEP_SHA256(buf, num, from, flen, NULL, 0);
break;
#endif
case RSA_SSLV23_PADDING:
i = RSA_padding_add_SSLv23(buf, num, from, flen);
break;
case RSA_NO_PADDING:
i = RSA_padding_add_none(buf, num, from, flen);
break;
default:
RSAerr(RSA_F_RSA_EAY_PUBLIC_ENCRYPT, RSA_R_UNKNOWN_PADDING_TYPE);
goto err;
}
if (i <= 0) goto err;
if (BN_bin2bn(buf, num, f) == NULL) goto err;
if (BN_ucmp(f, rsa->n) >= 0)
{
/* usually the padding functions would catch this */
RSAerr(RSA_F_RSA_EAY_PUBLIC_ENCRYPT, RSA_R_DATA_TOO_LARGE_FOR_MODULUS);
goto err;
}
if (rsa->flags & RSA_FLAG_CACHE_PUBLIC)
if (!BN_MONT_CTX_set_locked(&rsa->_method_mod_n, CRYPTO_LOCK_RSA, rsa->n, ctx))
goto err;
if (!rsa->meth->bn_mod_exp(ret, f, rsa->e, rsa->n, ctx,
rsa->_method_mod_n)) goto err;
/* put in leading 0 bytes if the number is less than the
* length of the modulus */
j = BN_num_bytes(ret);
i = BN_bn2bin(ret, &(to[num - j]));
for (k = 0; k<(num - i); k++)
to[k] = 0;
r = num;
err:
if (ctx != NULL)
{
BN_CTX_end(ctx);
BN_CTX_free(ctx);
}
if (buf != NULL)
{
OPENSSL_cleanse(buf, num);
OPENSSL_free(buf);
}
return(r);
}
int RSA_public_encrypt_sha256(int flen, const unsigned char *from, unsigned char *to,
RSA *rsa, int padding)
{
return(RSA_eay_public_encrypt_SHA256(flen, from, to, rsa, padding));
}
Just add these few functions and call RSA_public_encrypt_sha256 instead of RSA_public_encrypt and voila you have RSA_OAEP_SHA256
Well i know this is abusing the openssl code, but this is a solution if you cannot compile openssl lib yourself, like i cannot because i received this as a part of an ARM platform
All thanks go to JARIQ in the answer below!
Thank you!

I am not sure about the OpenSSL API but in PKCS#11 API when you are using RSA encryption with OAEP padding you can specify message digest algorithm and also a mask generation function as you can see in my code sample (take a look at _03_EncryptAndDecryptSinglePartOaepTest() method) . It is written in C# but I believe it should be easily understandable. However I have never tried anything else than SHA1.
More information can be found in RFC 3447 and PKCS#11 specification (chapter 12.1.7 and chapter 12.1.8).
EDIT for OpenSSL:
In OpenSSL RSA encryption with public key and OAEP padding is performed in this order:
you need to pass RSA_PKCS1_OAEP_PADDING flag to function RSA_public_encrypt() implemented in rsa_crpt.c
RSA_public_encrypt() then calls function RSA_eay_public_encrypt() implemented in rsa_eay.c (unless you are using some cryptographic hardware device via ENGINE)
RSA_eay_public_encrypt() then calls function RSA_padding_add_PKCS1_OAEP() implemented in rsa_oaep.c
This uses SHA1 which seems to be currently the only option implemented in OpenSSL but I believe it should be possible to slightly modify code in rsa_oaep.c file to achieve what you need.

Related

Openssl Encrypt/Decrypt does not work with base64

I have some openssl code that encrypts and decrypts a file.
Initially I wrote it so the encrypted file is base64 encoded. The problem is certain file sizes are reduced on decryption. i.e I lose upto 16 characters in the decrypted based on the original file
If the file is less that 16 bytes it is fine, greater than that and I start losing characters on decryption with and error on the final decrypt step.
I assumed initially it was a padding issue, but adding padding does bot fix however If I remove the base64 encoding it is fine however.
This is based on open_ssl 1.1.1s
int BIO_enc_header(BIO* file, unsigned char* Salt)
{
RAND_bytes(Salt, SALTSIZE);
int len=BIO_write(file,MAGIC, MAGIC_SIZE);
len=BIO_write(file,Salt, SALTSIZE);
return 0;
}
int BIO_dec_header(BIO* file, unsigned char* Salt)
{
int mlen = 0;
char mBuff[MAGIC_SIZE + 1] = { 0 };
mlen = BIO_read(file,mBuff, MAGIC_SIZE);
if (mlen == MAGIC_SIZE) {
/* Check Magic size */
int salt_len = BIO_read(file,Salt,SALTSIZE);
if (salt_len != SALTSIZE) {
return -1;
}
}
return 0;
}
//#define BASE64 1
int do_crypt_bio(BIO* bio_in, BIO* bio_out, int do_encrypt, unsigned char* szPassword)
{
int Result = OK;
unsigned char salt[8] = { 0 };
int inByte = 0, outByte = 0;
//int Padding;
#ifdef BASE64
BIO *b64 = BIO_new(BIO_f_base64());
if (!b64) {
return ERROR;
}
#endif
#ifdef BASE64
if (do_encrypt) {
/*out base64*/
bio_out = BIO_push(b64, bio_out);
BIO_set_flags(bio_out, BIO_FLAGS_BASE64_NO_NL);
}else {
/* read base 64*/
bio_in = BIO_push(b64, bio_in);
BIO_set_flags(bio_in, BIO_FLAGS_BASE64_NO_NL);
}
#endif
/* Allow enough space in output buffer for additional block */
unsigned char inbuf[ENC_BUFF_SIZE+ EVP_MAX_BLOCK_LENGTH], outbuf[ENC_BUFF_SIZE + EVP_MAX_BLOCK_LENGTH];
int inlen, outlen;
EVP_CIPHER_CTX* ctx=NULL;
#define AES_256_KEY_SIZE 32
unsigned char key[AES_256_KEY_SIZE] = { 0 };
unsigned char iv[AES_BLOCK_SIZE] = { 0 };
int rounds = 5;
int Size=EVP_CIPHER_block_size(EVP_aes_256_cbc());
if (do_encrypt) {
BIO_enc_header(bio_out, salt);
}else {
BIO_dec_header(bio_in, salt);
}
if (!EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha256(), salt, szPassword, strlen((char*)szPassword), rounds, key, iv)) {
#ifdef BASE64
BIO_free(b64);
#endif
return ERROR;
}
/* Don't set key or IV right away; we want to check lengths */
ctx = EVP_CIPHER_CTX_new();
if (!ctx) {
#ifdef BASE64
BIO_free(b64);
#endif
return ERROR;
}
int CipherResult=EVP_CipherInit_ex(ctx, EVP_aes_256_cbc(), NULL, NULL, NULL, do_encrypt);
CipherResult =EVP_CipherInit_ex(ctx, NULL, NULL, key, iv, do_encrypt); /* Now we can set key and IV */
//EVP_CIPHER_CTX_set_padding(ctx, 32);
int len;
for (;;) {
memset(inbuf, 0, ENC_BUFF_SIZE+EVP_MAX_BLOCK_LENGTH);
inlen = BIO_read(bio_in,inbuf, ENC_BUFF_SIZE);
//inByte += inlen;
if (inlen <= 0)break;
if (!EVP_CipherUpdate(ctx, outbuf, &outlen, inbuf, inlen)) {
/* Error */
Result = ERROR;
goto error;
}
len=BIO_write(bio_out,outbuf,outlen);
//outByte += outlen;
}
int f_len;
if (!EVP_CipherFinal_ex(ctx, outbuf, &f_len)) {
/* Error */
Result = ERROR;
ERR_print_errors_fp(stderr);
goto error;
}
len= BIO_write(bio_out, outbuf, f_len);
//outByte += f_len;
len = len;
error:
EVP_CIPHER_CTX_free(ctx);
#ifdef BASE64
BIO_free(b64);
#endif
return Result;
}
int EncryptFile(char* input, char *encrypted,char* password)
{
int Result = OK;
BIO* bio_in = BIO_new_file(input, "rb");
if (!bio_in) {
return ERROR;
}
BIO* bio_out = BIO_new_file(encrypted, "wb+");
if (!bio_out) {
BIO_free(bio_in);
return ERROR;
}
Result=do_crypt_bio(bio_in, bio_out, 1, (unsigned char*)password);
BIO_free(bio_in);
BIO_free(bio_out);
return Result;
}
int DecryptFile(char* encrypted, char* output, char* password)
{
int Result = OK;
BIO* bio_in = BIO_new_file(encrypted, "rb");
if (!bio_in) {
return ERROR;
}
BIO* bio_out = BIO_new_file(output, "wb+");
if (!bio_out) {
BIO_free(bio_in);
return ERROR;
}
Result=do_crypt_bio(bio_in, bio_out, 0, (unsigned char*)password);
BIO_free(bio_in);
BIO_free(bio_out);
return Result;
}

AES/CFB8 with OpenSSL

I recently started a small side-project to recreate the Minecraft Server in C++ but I stumbled on a problem.
I need to use the AES/CFB8 Cipher according to this link with continuous updating (not finished and restarted every packet).
I surfed trough the internet to try to find a proper solution but could not figure out one that was actually working or it was using the command line interface.
Any help is welcomed !
Cipher.h
class Cipher {
public:
enum CipherType {
ENCRYPT,
DECRYPT
};
private:
EVP_CIPHER_CTX* ctx;
CipherType type;
bool hasFinished;
public:
Cipher(const char* key, const char* iv, CipherType type);
~Cipher();
int update(char* in, int inl, char* out);
int final(char* out);
int getMinimumBufLength(int size);
};
Cipher.cpp
Cipher::Cipher(const char* key, const char* iv, CipherType type) : type(type), hasFinished(false)
{
ctx = EVP_CIPHER_CTX_new();
EVP_CIPHER_CTX_init(ctx);
if(type == CipherType::ENCRYPT)
EVP_EncryptInit_ex(ctx, EVP_aes_256_cfb8(), nullptr, (unsigned char*)key, (unsigned char*)iv);
else
EVP_DecryptInit_ex(ctx, EVP_aes_256_cfb8(), nullptr, (unsigned char*)key, (unsigned char*)iv);
}
Cipher::~Cipher()
{
EVP_CIPHER_CTX_free(ctx);
}
int Cipher::getMinimumBufLength(int size)
{
return type == CipherType::DECRYPT ? size : size + AES_BLOCK_SIZE * 2;
}
int Cipher::update(char* in, int inl, char* out)
{
int len;
EVP_CipherUpdate(ctx, (unsigned char*)out, &len, (unsigned char*)in, inl);
return len;
}
int Cipher::final(char* out)
{
int len;
EVP_EncryptFinal_ex(ctx, (unsigned char*)out, &len);
return len;
}
Cipher-test.cpp
TEST_CASE("Cipher AES/CFB8")
{
char* key = "AAAAAAAAAZEFRGUINJFKDFIVJ";
char* iv = "AUJVFEUB";
Cipher encryptionCipher(key, iv, Cipher::CipherType::ENCRYPT);
Cipher decryptionCipher(key, iv, Cipher::CipherType::DECRYPT);
std::string data = "Hello World ! This is some soon to be encrypted string !";
char encData[encryptionCipher.getMinimumBufLength(data.size() + 1)];
int outLen = encryptionCipher.update(data.data(), data.size() + 1, encData);
int fLen = encryptionCipher.final(encData + outLen);
CHECK(data != std::string(encData));
char decData[decryptionCipher.getMinimumBufLength(outLen + fLen)];
outLen = decryptionCipher.update(encData, outLen + fLen, decData);
decryptionCipher.final(decData + outLen);
CHECK(data == std::string(decData));
}
Thanks in advance
EDIT: I am just wondering if there was a need for supplementary code because of the "continuous" aspect of the Cipher.

OpenSSL's EVP_DigestSignFinal() creates invalid signatures

I wrote the following code that should return a signature of the provided std::string signed using an EC private key.
std::string msg = "test";
size_t messageLength = msg.length();
size_t encMessageLength;
unsigned char* encMessage;
signMessage(msg, evp_private_key, messageLength, &encMessageLength, &encMessage);
bool signMessage(std::string msg, EVP_PKEY *&key, size_t messageLength, size_t *encMessageLength, unsigned char** encMessage) {
unsigned char * u_msg = (unsigned char*) msg.c_str();
EVP_MD_CTX* signCtx = EVP_MD_CTX_new();
if (EVP_DigestSignInit(signCtx,NULL, EVP_sha256(), NULL, key) <= 0) {
return NULL;
}
if (EVP_DigestSignUpdate(signCtx, u_msg, messageLength) <= 0) {
return NULL;
}
if (EVP_DigestSignFinal(signCtx, NULL, encMessageLength) <= 0) {
return NULL;
}
*encMessage = (unsigned char*)malloc(*encMessageLength);
if (EVP_DigestSignFinal(signCtx, *encMessage, encMessageLength) <= 0) {
return NULL;
}
EVP_MD_CTX_free(signCtx);
return true;
}
*encMessage is freed after the function call.
The length of encMessage varies greatly between 3 and 139.
An example output casted and printed as char* as well as hex looks like this:
�D�b���w�x�VS:������dƀ7�#����v�B%M�X2�ky���!�h�};J>�!���h�ħ�n����/�j�U���aL����z�X���>�2�>
Length: 138
hex: 30818702414fa9b7d8cac465525add0451784bc751544702edd0e24bdb0dca44f362fc80a11b3777e78316788e56533a91c0a1eb058bf1a564c68037900640cc13b9aca776c4024201254dd55832a16b799ad2f021f468a07d3b4a3e801321bba284688fc4a7c0156e86da1e84952f8c6ab755da1dd3ec614cec10bea1bc7aac58affa9c3efb32990f3e
Another function call:
msg: 0��B
Length: 5
hex: 3081880242
What am I doing wrong?

Mupdf Encryption Decryption Issue

I am facing some issues while creating a build for Mupdf to read encrypted pdf files. I am seeing the file, pdf_crypt.c to understand how this can be done. But i cannot understand how to do it.
If i put it in plain terms,
I will be encrypting my pdf with a AES 256 bit key.
Where should i put the AES key in pdf_crypt.c, so that mupdf can read the file by decrypting it at runtime ?
I am posting the pdf_crypt.c file for reference..
#include "mupdf/pdf.h"
enum
{
PDF_CRYPT_NONE,
PDF_CRYPT_RC4,
PDF_CRYPT_AESV2,
PDF_CRYPT_AESV3,
PDF_CRYPT_UNKNOWN,
};
enum
{
PDF_PERM_PRINT = 1 << 2,
PDF_PERM_CHANGE = 1 << 3,
PDF_PERM_COPY = 1 << 4,
PDF_PERM_NOTES = 1 << 5,
PDF_PERM_FILL_FORM = 1 << 8,
PDF_PERM_ACCESSIBILITY = 1 << 9,
PDF_PERM_ASSEMBLE = 1 << 10,
PDF_PERM_HIGH_RES_PRINT = 1 << 11,
PDF_DEFAULT_PERM_FLAGS = 0xfffc
};
typedef struct pdf_crypt_filter_s pdf_crypt_filter;
struct pdf_crypt_filter_s
{
int method;
int length;
};
struct pdf_crypt_s
{
pdf_obj *id;
int v;
int length;
pdf_obj *cf;
pdf_crypt_filter stmf;
pdf_crypt_filter strf;
int r;
unsigned char o[48];
unsigned char u[48];
unsigned char oe[32];
unsigned char ue[32];
int p;
int encrypt_metadata;
unsigned char key[32]; /* decryption key generated from password */
};
static void pdf_parse_crypt_filter(fz_context *ctx, pdf_crypt_filter *cf, pdf_crypt *crypt, pdf_obj *name);
/*
* Create crypt object for decrypting strings and streams
* given the Encryption and ID objects.
*/
pdf_crypt *
pdf_new_crypt(fz_context *ctx, pdf_obj *dict, pdf_obj *id)
{
pdf_crypt *crypt;
pdf_obj *obj;
crypt = fz_malloc_struct(ctx, pdf_crypt);
/* Common to all security handlers (PDF 1.7 table 3.18) */
obj = pdf_dict_get(ctx, dict, PDF_NAME_Filter);
if (!pdf_is_name(ctx, obj))
{
pdf_drop_crypt(ctx, crypt);
fz_throw(ctx, FZ_ERROR_GENERIC, "unspecified encryption handler");
}
if (!pdf_name_eq(ctx, PDF_NAME_Standard, obj) != 0)
{
pdf_drop_crypt(ctx, crypt);
fz_throw(ctx, FZ_ERROR_GENERIC, "unknown encryption handler: '%s'", pdf_to_name(ctx, obj));
}
crypt->v = 0;
obj = pdf_dict_get(ctx, dict, PDF_NAME_V);
if (pdf_is_int(ctx, obj))
crypt->v = pdf_to_int(ctx, obj);
if (crypt->v != 1 && crypt->v != 2 && crypt->v != 4 && crypt->v != 5)
{
pdf_drop_crypt(ctx, crypt);
fz_throw(ctx, FZ_ERROR_GENERIC, "unknown encryption version");
}
/* Standard security handler (PDF 1.7 table 3.19) */
obj = pdf_dict_get(ctx, dict, PDF_NAME_R);
if (pdf_is_int(ctx, obj))
crypt->r = pdf_to_int(ctx, obj);
else if (crypt->v <= 4)
{
fz_warn(ctx, "encryption dictionary missing revision value, guessing...");
if (crypt->v < 2)
crypt->r = 2;
else if (crypt->v == 2)
crypt->r = 3;
else if (crypt->v == 4)
crypt->r = 4;
}
else
{
pdf_drop_crypt(ctx, crypt);
fz_throw(ctx, FZ_ERROR_GENERIC, "encryption dictionary missing version and revision value");
}
if (crypt->r < 1 || crypt->r > 6)
{
int r = crypt->r;
pdf_drop_crypt(ctx, crypt);
fz_throw(ctx, FZ_ERROR_GENERIC, "unknown crypt revision %d", r);
}
obj = pdf_dict_get(ctx, dict, PDF_NAME_O);
if (pdf_is_string(ctx, obj) && pdf_to_str_len(ctx, obj) == 32)
memcpy(crypt->o, pdf_to_str_buf(ctx, obj), 32);
/* /O and /U are supposed to be 48 bytes long for revision 5 and 6, they're often longer, though */
else if (crypt->r >= 5 && pdf_is_string(ctx, obj) && pdf_to_str_len(ctx, obj) >= 48)
memcpy(crypt->o, pdf_to_str_buf(ctx, obj), 48);
else
{
pdf_drop_crypt(ctx, crypt);
fz_throw(ctx, FZ_ERROR_GENERIC, "encryption dictionary missing owner password");
}
obj = pdf_dict_get(ctx, dict, PDF_NAME_U);
if (pdf_is_string(ctx, obj) && pdf_to_str_len(ctx, obj) == 32)
memcpy(crypt->u, pdf_to_str_buf(ctx, obj), 32);
/* /O and /U are supposed to be 48 bytes long for revision 5 and 6, they're often longer, though */
else if (crypt->r >= 5 && pdf_is_string(ctx, obj) && pdf_to_str_len(ctx, obj) >= 48)
memcpy(crypt->u, pdf_to_str_buf(ctx, obj), 48);
else if (pdf_is_string(ctx, obj) && pdf_to_str_len(ctx, obj) < 32)
{
fz_warn(ctx, "encryption password key too short (%d)", pdf_to_str_len(ctx, obj));
memcpy(crypt->u, pdf_to_str_buf(ctx, obj), pdf_to_str_len(ctx, obj));
}
else
{
pdf_drop_crypt(ctx, crypt);
fz_throw(ctx, FZ_ERROR_GENERIC, "encryption dictionary missing user password");
}
obj = pdf_dict_get(ctx, dict, PDF_NAME_P);
if (pdf_is_int(ctx, obj))
crypt->p = pdf_to_int(ctx, obj);
else
{
fz_warn(ctx, "encryption dictionary missing permissions");
crypt->p = 0xfffffffc;
}
if (crypt->r == 5 || crypt->r == 6)
{
obj = pdf_dict_get(ctx, dict, PDF_NAME_OE);
if (!pdf_is_string(ctx, obj) || pdf_to_str_len(ctx, obj) != 32)
{
pdf_drop_crypt(ctx, crypt);
fz_throw(ctx, FZ_ERROR_GENERIC, "encryption dictionary missing owner encryption key");
}
memcpy(crypt->oe, pdf_to_str_buf(ctx, obj), 32);
obj = pdf_dict_get(ctx, dict, PDF_NAME_UE);
if (!pdf_is_string(ctx, obj) || pdf_to_str_len(ctx, obj) != 32)
{
pdf_drop_crypt(ctx, crypt);
fz_throw(ctx, FZ_ERROR_GENERIC, "encryption dictionary missing user encryption key");
}
memcpy(crypt->ue, pdf_to_str_buf(ctx, obj), 32);
}
crypt->encrypt_metadata = 1;
obj = pdf_dict_get(ctx, dict, PDF_NAME_EncryptMetadata);
if (pdf_is_bool(ctx, obj))
crypt->encrypt_metadata = pdf_to_bool(ctx, obj);
/* Extract file identifier string */
if (pdf_is_array(ctx, id) && pdf_array_len(ctx, id) == 2)
{
obj = pdf_array_get(ctx, id, 0);
if (pdf_is_string(ctx, obj))
crypt->id = pdf_keep_obj(ctx, obj);
}
else
fz_warn(ctx, "missing file identifier, may not be able to do decryption");
/* Determine encryption key length */
crypt->length = 40;
if (crypt->v == 2 || crypt->v == 4)
{
obj = pdf_dict_get(ctx, dict, PDF_NAME_Length);
if (pdf_is_int(ctx, obj))
crypt->length = pdf_to_int(ctx, obj);
/* work-around for pdf generators that assume length is in bytes */
if (crypt->length < 40)
crypt->length = crypt->length * 8;
if (crypt->length % 8 != 0)
{
pdf_drop_crypt(ctx, crypt);
fz_throw(ctx, FZ_ERROR_GENERIC, "invalid encryption key length");
}
if (crypt->length < 40 || crypt->length > 128)
{
pdf_drop_crypt(ctx, crypt);
fz_throw(ctx, FZ_ERROR_GENERIC, "invalid encryption key length");
}
}
if (crypt->v == 5)
crypt->length = 256;
if (crypt->v == 1 || crypt->v == 2)
{
crypt->stmf.method = PDF_CRYPT_RC4;
crypt->stmf.length = crypt->length;
crypt->strf.method = PDF_CRYPT_RC4;
crypt->strf.length = crypt->length;
}
if (crypt->v == 4 || crypt->v == 5)
{
crypt->stmf.method = PDF_CRYPT_NONE;
crypt->stmf.length = crypt->length;
crypt->strf.method = PDF_CRYPT_NONE;
crypt->strf.length = crypt->length;
obj = pdf_dict_get(ctx, dict, PDF_NAME_CF);
if (pdf_is_dict(ctx, obj))
{
crypt->cf = pdf_keep_obj(ctx, obj);
}
else
{
crypt->cf = NULL;
}
fz_try(ctx)
{
obj = pdf_dict_get(ctx, dict, PDF_NAME_StmF);
if (pdf_is_name(ctx, obj))
pdf_parse_crypt_filter(ctx, &crypt->stmf, crypt, obj);
obj = pdf_dict_get(ctx, dict, PDF_NAME_StrF);
if (pdf_is_name(ctx, obj))
pdf_parse_crypt_filter(ctx, &crypt->strf, crypt, obj);
}
fz_catch(ctx)
{
pdf_drop_crypt(ctx, crypt);
fz_rethrow_message(ctx, "cannot parse string crypt filter (%d %d R)", pdf_to_num(ctx, obj), pdf_to_gen(ctx, obj));
}
/* in crypt revision 4, the crypt filter determines the key length */
if (crypt->strf.method != PDF_CRYPT_NONE)
crypt->length = crypt->stmf.length;
}
return crypt;
}
void
pdf_drop_crypt(fz_context *ctx, pdf_crypt *crypt)
{
pdf_drop_obj(ctx, crypt->id);
pdf_drop_obj(ctx, crypt->cf);
fz_free(ctx, crypt);
}
/*
* Parse a CF dictionary entry (PDF 1.7 table 3.22)
*/
static void
pdf_parse_crypt_filter(fz_context *ctx, pdf_crypt_filter *cf, pdf_crypt *crypt, pdf_obj *name)
{
pdf_obj *obj;
pdf_obj *dict;
int is_identity = (pdf_name_eq(ctx, name, PDF_NAME_Identity));
int is_stdcf = (!is_identity && pdf_name_eq(ctx, name, PDF_NAME_StdCF));
if (!is_identity && !is_stdcf)
fz_throw(ctx, FZ_ERROR_GENERIC, "Crypt Filter not Identity or StdCF (%d %d R)", pdf_to_num(ctx, crypt->cf), pdf_to_gen(ctx, crypt->cf));
cf->method = PDF_CRYPT_NONE;
cf->length = crypt->length;
if (!crypt->cf)
{
cf->method = (is_identity ? PDF_CRYPT_NONE : PDF_CRYPT_RC4);
return;
}
dict = pdf_dict_get(ctx, crypt->cf, name);
if (!pdf_is_dict(ctx, dict))
fz_throw(ctx, FZ_ERROR_GENERIC, "cannot parse crypt filter (%d %d R)", pdf_to_num(ctx, crypt->cf), pdf_to_gen(ctx, crypt->cf));
obj = pdf_dict_get(ctx, dict, PDF_NAME_CFM);
if (pdf_is_name(ctx, obj))
{
if (pdf_name_eq(ctx, PDF_NAME_None, obj))
cf->method = PDF_CRYPT_NONE;
else if (pdf_name_eq(ctx, PDF_NAME_V2, obj))
cf->method = PDF_CRYPT_RC4;
else if (pdf_name_eq(ctx, PDF_NAME_AESV2, obj))
cf->method = PDF_CRYPT_AESV2;
else if (pdf_name_eq(ctx, PDF_NAME_AESV3, obj))
cf->method = PDF_CRYPT_AESV3;
else
fz_warn(ctx, "unknown encryption method: %s", pdf_to_name(ctx, obj));
}
obj = pdf_dict_get(ctx, dict, PDF_NAME_Length);
if (pdf_is_int(ctx, obj))
cf->length = pdf_to_int(ctx, obj);
/* the length for crypt filters is supposed to be in bytes not bits */
if (cf->length < 40)
cf->length = cf->length * 8;
if ((cf->length % 8) != 0)
fz_throw(ctx, FZ_ERROR_GENERIC, "invalid key length: %d", cf->length);
if ((crypt->r == 1 || crypt->r == 2 || crypt->r == 3 || crypt->r == 4) &&
(cf->length < 0 || cf->length > 128))
fz_throw(ctx, FZ_ERROR_GENERIC, "invalid key length: %d", cf->length);
if ((crypt->r == 5 || crypt->r == 6) && cf->length != 256)
fz_throw(ctx, FZ_ERROR_GENERIC, "invalid key length: %d", cf->length);
}
/*
* Compute an encryption key (PDF 1.7 algorithm 3.2)
*/
static const unsigned char padding[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
};
static void
pdf_compute_encryption_key(fz_context *ctx, pdf_crypt *crypt, unsigned char *password, int pwlen, unsigned char *key)
{
unsigned char buf[32];
unsigned int p;
int i, n;
fz_md5 md5;
n = crypt->length / 8;
/* Step 1 - copy and pad password string */
if (pwlen > 32)
pwlen = 32;
memcpy(buf, password, pwlen);
memcpy(buf + pwlen, padding, 32 - pwlen);
/* Step 2 - init md5 and pass value of step 1 */
fz_md5_init(&md5);
fz_md5_update(&md5, buf, 32);
/* Step 3 - pass O value */
fz_md5_update(&md5, crypt->o, 32);
/* Step 4 - pass P value as unsigned int, low-order byte first */
p = (unsigned int) crypt->p;
buf[0] = (p) & 0xFF;
buf[1] = (p >> 8) & 0xFF;
buf[2] = (p >> 16) & 0xFF;
buf[3] = (p >> 24) & 0xFF;
fz_md5_update(&md5, buf, 4);
/* Step 5 - pass first element of ID array */
fz_md5_update(&md5, (unsigned char *)pdf_to_str_buf(ctx, crypt->id), pdf_to_str_len(ctx, crypt->id));
/* Step 6 (revision 4 or greater) - if metadata is not encrypted pass 0xFFFFFFFF */
if (crypt->r >= 4)
{
if (!crypt->encrypt_metadata)
{
buf[0] = 0xFF;
buf[1] = 0xFF;
buf[2] = 0xFF;
buf[3] = 0xFF;
fz_md5_update(&md5, buf, 4);
}
}
/* Step 7 - finish the hash */
fz_md5_final(&md5, buf);
/* Step 8 (revision 3 or greater) - do some voodoo 50 times */
if (crypt->r >= 3)
{
for (i = 0; i < 50; i++)
{
fz_md5_init(&md5);
fz_md5_update(&md5, buf, n);
fz_md5_final(&md5, buf);
}
}
/* Step 9 - the key is the first 'n' bytes of the result */
memcpy(key, buf, n);
}
/*
* Compute an encryption key (PDF 1.7 ExtensionLevel 3 algorithm 3.2a)
*/
static void
pdf_compute_encryption_key_r5(fz_context *ctx, pdf_crypt *crypt, unsigned char *password, int pwlen, int ownerkey, unsigned char *validationkey)
{
unsigned char buffer[128 + 8 + 48];
fz_sha256 sha256;
fz_aes aes;
/* Step 2 - truncate UTF-8 password to 127 characters */
if (pwlen > 127)
pwlen = 127;
/* Step 3/4 - test password against owner/user key and compute encryption key */
memcpy(buffer, password, pwlen);
if (ownerkey)
{
memcpy(buffer + pwlen, crypt->o + 32, 8);
memcpy(buffer + pwlen + 8, crypt->u, 48);
}
else
memcpy(buffer + pwlen, crypt->u + 32, 8);
fz_sha256_init(&sha256);
fz_sha256_update(&sha256, buffer, pwlen + 8 + (ownerkey ? 48 : 0));
fz_sha256_final(&sha256, validationkey);
/* Step 3.5/4.5 - compute file encryption key from OE/UE */
memcpy(buffer + pwlen, crypt->u + 40, 8);
fz_sha256_init(&sha256);
fz_sha256_update(&sha256, buffer, pwlen + 8);
fz_sha256_final(&sha256, buffer);
/* clear password buffer and use it as iv */
memset(buffer + 32, 0, sizeof(buffer) - 32);
if (aes_setkey_dec(&aes, buffer, crypt->length))
fz_throw(ctx, FZ_ERROR_GENERIC, "AES key init failed (keylen=%d)", crypt->length);
aes_crypt_cbc(&aes, AES_DECRYPT, 32, buffer + 32, ownerkey ? crypt->oe : crypt->ue, crypt->key);
}
/*
* Compute an encryption key (PDF 1.7 ExtensionLevel 8 algorithm)
*
* Adobe has not yet released the details, so the algorithm reference is:
* http://esec-lab.sogeti.com/post/The-undocumented-password-validation-algorithm-of-Adobe-Reader-X
*/
static void
pdf_compute_hardened_hash_r6(fz_context *ctx, unsigned char *password, int pwlen, unsigned char salt[16], unsigned char *ownerkey, unsigned char hash[32])
{
unsigned char data[(128 + 64 + 48) * 64];
unsigned char block[64];
int block_size = 32;
int data_len = 0;
int i, j, sum;
fz_sha256 sha256;
fz_sha384 sha384;
fz_sha512 sha512;
fz_aes aes;
/* Step 1: calculate initial data block */
fz_sha256_init(&sha256);
fz_sha256_update(&sha256, password, pwlen);
fz_sha256_update(&sha256, salt, 8);
if (ownerkey)
fz_sha256_update(&sha256, ownerkey, 48);
fz_sha256_final(&sha256, block);
for (i = 0; i < 64 || i < data[data_len * 64 - 1] + 32; i++)
{
/* Step 2: repeat password and data block 64 times */
memcpy(data, password, pwlen);
memcpy(data + pwlen, block, block_size);
if (ownerkey)
memcpy(data + pwlen + block_size, ownerkey, 48);
data_len = pwlen + block_size + (ownerkey ? 48 : 0);
for (j = 1; j < 64; j++)
memcpy(data + j * data_len, data, data_len);
/* Step 3: encrypt data using data block as key and iv */
if (aes_setkey_enc(&aes, block, 128))
fz_throw(ctx, FZ_ERROR_GENERIC, "AES key init failed (keylen=%d)", 128);
aes_crypt_cbc(&aes, AES_ENCRYPT, data_len * 64, block + 16, data, data);
/* Step 4: determine SHA-2 hash size for this round */
for (j = 0, sum = 0; j < 16; j++)
sum += data[j];
/* Step 5: calculate data block for next round */
block_size = 32 + (sum % 3) * 16;
switch (block_size)
{
case 32:
fz_sha256_init(&sha256);
fz_sha256_update(&sha256, data, data_len * 64);
fz_sha256_final(&sha256, block);
break;
case 48:
fz_sha384_init(&sha384);
fz_sha384_update(&sha384, data, data_len * 64);
fz_sha384_final(&sha384, block);
break;
case 64:
fz_sha512_init(&sha512);
fz_sha512_update(&sha512, data, data_len * 64);
fz_sha512_final(&sha512, block);
break;
}
}
memset(data, 0, sizeof(data));
memcpy(hash, block, 32);
}
static void
pdf_compute_encryption_key_r6(fz_context *ctx, pdf_crypt *crypt, unsigned char *password, int pwlen, int ownerkey, unsigned char *validationkey)
{
unsigned char hash[32];
unsigned char iv[16];
fz_aes aes;
if (pwlen > 127)
pwlen = 127;
pdf_compute_hardened_hash_r6(ctx, password, pwlen,
(ownerkey ? crypt->o : crypt->u) + 32,
ownerkey ? crypt->u : NULL, validationkey);
pdf_compute_hardened_hash_r6(ctx, password, pwlen,
crypt->u + 40, NULL, hash);
memset(iv, 0, sizeof(iv));
if (aes_setkey_dec(&aes, hash, 256))
fz_throw(ctx, FZ_ERROR_GENERIC, "AES key init failed (keylen=256)");
aes_crypt_cbc(&aes, AES_DECRYPT, 32, iv,
ownerkey ? crypt->oe : crypt->ue, crypt->key);
}
/*
* Computing the user password (PDF 1.7 algorithm 3.4 and 3.5)
* Also save the generated key for decrypting objects and streams in crypt->key.
*/
static void
pdf_compute_user_password(fz_context *ctx, pdf_crypt *crypt, unsigned char *password, int pwlen, unsigned char *output)
{
if (crypt->r == 2)
{
fz_arc4 arc4;
pdf_compute_encryption_key(ctx, crypt, password, pwlen, crypt->key);
fz_arc4_init(&arc4, crypt->key, crypt->length / 8);
fz_arc4_encrypt(&arc4, output, padding, 32);
}
if (crypt->r == 3 || crypt->r == 4)
{
unsigned char xor[32];
unsigned char digest[16];
fz_md5 md5;
fz_arc4 arc4;
int i, x, n;
n = crypt->length / 8;
pdf_compute_encryption_key(ctx, crypt, password, pwlen, crypt->key);
fz_md5_init(&md5);
fz_md5_update(&md5, padding, 32);
fz_md5_update(&md5, (unsigned char*)pdf_to_str_buf(ctx, crypt->id), pdf_to_str_len(ctx, crypt->id));
fz_md5_final(&md5, digest);
fz_arc4_init(&arc4, crypt->key, n);
fz_arc4_encrypt(&arc4, output, digest, 16);
for (x = 1; x <= 19; x++)
{
for (i = 0; i < n; i++)
xor[i] = crypt->key[i] ^ x;
fz_arc4_init(&arc4, xor, n);
fz_arc4_encrypt(&arc4, output, output, 16);
}
memcpy(output + 16, padding, 16);
}
if (crypt->r == 5)
{
pdf_compute_encryption_key_r5(ctx, crypt, password, pwlen, 0, output);
}
if (crypt->r == 6)
{
pdf_compute_encryption_key_r6(ctx, crypt, password, pwlen, 0, output);
}
}
/*
* Authenticating the user password (PDF 1.7 algorithm 3.6
* and ExtensionLevel 3 algorithm 3.11)
* This also has the side effect of saving a key generated
* from the password for decrypting objects and streams.
*/
static int
pdf_authenticate_user_password(fz_context *ctx, pdf_crypt *crypt, unsigned char *password, int pwlen)
{
unsigned char output[32];
pdf_compute_user_password(ctx, crypt, password, pwlen, output);
if (crypt->r == 2 || crypt->r == 5 || crypt->r == 6)
return memcmp(output, crypt->u, 32) == 0;
if (crypt->r == 3 || crypt->r == 4)
return memcmp(output, crypt->u, 16) == 0;
return 0;
}
/*
* Authenticating the owner password (PDF 1.7 algorithm 3.7
* and ExtensionLevel 3 algorithm 3.12)
* Generates the user password from the owner password
* and calls pdf_authenticate_user_password.
*/
static int
pdf_authenticate_owner_password(fz_context *ctx, pdf_crypt *crypt, unsigned char *ownerpass, int pwlen)
{
unsigned char pwbuf[32];
unsigned char key[32];
unsigned char xor[32];
unsigned char userpass[32];
int i, n, x;
fz_md5 md5;
fz_arc4 arc4;
if (crypt->r == 5)
{
/* PDF 1.7 ExtensionLevel 3 algorithm 3.12 */
pdf_compute_encryption_key_r5(ctx, crypt, ownerpass, pwlen, 1, key);
return !memcmp(key, crypt->o, 32);
}
else if (crypt->r == 6)
{
/* PDF 1.7 ExtensionLevel 8 algorithm */
pdf_compute_encryption_key_r6(ctx, crypt, ownerpass, pwlen, 1, key);
return !memcmp(key, crypt->o, 32);
}
n = crypt->length / 8;
/* Step 1 -- steps 1 to 4 of PDF 1.7 algorithm 3.3 */
/* copy and pad password string */
if (pwlen > 32)
pwlen = 32;
memcpy(pwbuf, ownerpass, pwlen);
memcpy(pwbuf + pwlen, padding, 32 - pwlen);
/* take md5 hash of padded password */
fz_md5_init(&md5);
fz_md5_update(&md5, pwbuf, 32);
fz_md5_final(&md5, key);
/* do some voodoo 50 times (Revision 3 or greater) */
if (crypt->r >= 3)
{
for (i = 0; i < 50; i++)
{
fz_md5_init(&md5);
fz_md5_update(&md5, key, 16);
fz_md5_final(&md5, key);
}
}
/* Step 2 (Revision 2) */
if (crypt->r == 2)
{
fz_arc4_init(&arc4, key, n);
fz_arc4_encrypt(&arc4, userpass, crypt->o, 32);
}
/* Step 2 (Revision 3 or greater) */
if (crypt->r >= 3)
{
memcpy(userpass, crypt->o, 32);
for (x = 0; x < 20; x++)
{
for (i = 0; i < n; i++)
xor[i] = key[i] ^ (19 - x);
fz_arc4_init(&arc4, xor, n);
fz_arc4_encrypt(&arc4, userpass, userpass, 32);
}
}
return pdf_authenticate_user_password(ctx, crypt, userpass, 32);
}
static void pdf_docenc_from_utf8(char *password, const char *utf8, int n)
{
int i = 0, k, c;
while (*utf8 && i + 1 < n)
{
utf8 += fz_chartorune(&c, utf8);
for (k = 0; k < 256; k++)
{
if (c == pdf_doc_encoding[k])
{
password[i++] = k;
break;
}
}
/* FIXME: drop characters that can't be encoded or return an error? */
}
password[i] = 0;
}
static void pdf_saslprep_from_utf8(char *password, const char *utf8, int n)
{
/* TODO: stringprep with SALSprep profile */
fz_strlcpy(password, utf8, n);
}
int
pdf_authenticate_password(fz_context *ctx, pdf_document *doc, const char *pwd_utf8)
{
char password[2048];
if (doc->crypt)
{
password[0] = 0;
if (pwd_utf8)
{
if (doc->crypt->r <= 4)
pdf_docenc_from_utf8(password, pwd_utf8, sizeof password);
else
pdf_saslprep_from_utf8(password, pwd_utf8, sizeof password);
}
if (pdf_authenticate_user_password(ctx, doc->crypt, (unsigned char *)password, strlen(password)))
return 1;
if (pdf_authenticate_owner_password(ctx, doc->crypt, (unsigned char *)password, strlen(password)))
return 1;
return 0;
}
return 1;
}
int
pdf_needs_password(fz_context *ctx, pdf_document *doc)
{
if (!doc->crypt)
return 0;
if (pdf_authenticate_password(ctx, doc, ""))
return 0;
return 1;
}
int
pdf_has_permission(fz_context *ctx, pdf_document *doc, fz_permission p)
{
if (!doc->crypt)
return 1;
switch (p)
{
case FZ_PERMISSION_PRINT: return doc->crypt->p & PDF_PERM_PRINT;
case FZ_PERMISSION_COPY: return doc->crypt->p & PDF_PERM_COPY;
case FZ_PERMISSION_EDIT: return doc->crypt->p & PDF_PERM_CHANGE;
case FZ_PERMISSION_ANNOTATE: return doc->crypt->p & PDF_PERM_NOTES;
}
return 1;
}
unsigned char *
pdf_crypt_key(fz_context *ctx, pdf_document *doc)
{
if (doc->crypt)
return doc->crypt->key;
return NULL;
}
int
pdf_crypt_version(fz_context *ctx, pdf_document *doc)
{
if (doc->crypt)
return doc->crypt->v;
return 0;
}
int pdf_crypt_revision(fz_context *ctx, pdf_document *doc)
{
if (doc->crypt)
return doc->crypt->r;
return 0;
}
char *
pdf_crypt_method(fz_context *ctx, pdf_document *doc)
{
if (doc->crypt)
{
switch (doc->crypt->strf.method)
{
case PDF_CRYPT_NONE: return "None";
case PDF_CRYPT_RC4: return "RC4";
case PDF_CRYPT_AESV2: return "AES";
case PDF_CRYPT_AESV3: return "AES";
case PDF_CRYPT_UNKNOWN: return "Unknown";
}
}
return "None";
}
int
pdf_crypt_length(fz_context *ctx, pdf_document *doc)
{
if (doc->crypt)
return doc->crypt->length;
return 0;
}
/*
* PDF 1.7 algorithm 3.1 and ExtensionLevel 3 algorithm 3.1a
*
* Using the global encryption key that was generated from the
* password, create a new key that is used to decrypt individual
* objects and streams. This key is based on the object and
* generation numbers.
*/
static int
pdf_compute_object_key(pdf_crypt *crypt, pdf_crypt_filter *cf, int num, int gen, unsigned char *key, int max_len)
{
fz_md5 md5;
unsigned char message[5];
int key_len = crypt->length / 8;
if (key_len > max_len)
key_len = max_len;
if (cf->method == PDF_CRYPT_AESV3)
{
memcpy(key, crypt->key, key_len);
return key_len;
}
fz_md5_init(&md5);
fz_md5_update(&md5, crypt->key, key_len);
message[0] = (num) & 0xFF;
message[1] = (num >> 8) & 0xFF;
message[2] = (num >> 16) & 0xFF;
message[3] = (gen) & 0xFF;
message[4] = (gen >> 8) & 0xFF;
fz_md5_update(&md5, message, 5);
if (cf->method == PDF_CRYPT_AESV2)
fz_md5_update(&md5, (unsigned char *)"sAlT", 4);
fz_md5_final(&md5, key);
if (key_len + 5 > 16)
return 16;
return key_len + 5;
}
/*
* PDF 1.7 algorithm 3.1 and ExtensionLevel 3 algorithm 3.1a
*
* Decrypt all strings in obj modifying the data in-place.
* Recurse through arrays and dictionaries, but do not follow
* indirect references.
*/
static void
pdf_crypt_obj_imp(fz_context *ctx, pdf_crypt *crypt, pdf_obj *obj, unsigned char *key, int keylen)
{
unsigned char *s;
int i, n;
if (pdf_is_indirect(ctx, obj))
return;
if (pdf_is_string(ctx, obj))
{
s = (unsigned char *)pdf_to_str_buf(ctx, obj);
n = pdf_to_str_len(ctx, obj);
if (crypt->strf.method == PDF_CRYPT_RC4)
{
fz_arc4 arc4;
fz_arc4_init(&arc4, key, keylen);
fz_arc4_encrypt(&arc4, s, s, n);
}
if (crypt->strf.method == PDF_CRYPT_AESV2 || crypt->strf.method == PDF_CRYPT_AESV3)
{
if (n == 0)
{
/* Empty strings are permissible */
}
else if (n & 15 || n < 32)
fz_warn(ctx, "invalid string length for aes encryption");
else
{
unsigned char iv[16];
fz_aes aes;
memcpy(iv, s, 16);
if (aes_setkey_dec(&aes, key, keylen * 8))
fz_throw(ctx, FZ_ERROR_GENERIC, "AES key init failed (keylen=%d)", keylen * 8);
aes_crypt_cbc(&aes, AES_DECRYPT, n - 16, iv, s + 16, s);
/* delete space used for iv and padding bytes at end */
if (s[n - 17] < 1 || s[n - 17] > 16)
fz_warn(ctx, "aes padding out of range");
else
pdf_set_str_len(ctx, obj, n - 16 - s[n - 17]);
}
}
}
else if (pdf_is_array(ctx, obj))
{
n = pdf_array_len(ctx, obj);
for (i = 0; i < n; i++)
{
pdf_crypt_obj_imp(ctx, crypt, pdf_array_get(ctx, obj, i), key, keylen);
}
}
else if (pdf_is_dict(ctx, obj))
{
n = pdf_dict_len(ctx, obj);
for (i = 0; i < n; i++)
{
pdf_crypt_obj_imp(ctx, crypt, pdf_dict_get_val(ctx, obj, i), key, keylen);
}
}
}
void
pdf_crypt_obj(fz_context *ctx, pdf_crypt *crypt, pdf_obj *obj, int num, int gen)
{
unsigned char key[32];
int len;
len = pdf_compute_object_key(crypt, &crypt->strf, num, gen, key, 32);
pdf_crypt_obj_imp(ctx, crypt, obj, key, len);
}
Firstly, I should say a few words about licensing. MuPDF is licensed under 2 different licences.
The first is the GNU AGPL. This is a very strict license that places lots of requirements upon you for you to be able to use the code. The biggest requirement is that anyone that gets a copy of your app has the rights to demand the full source code for your entire app. This would clearly include any decryption key, meaning your DRM would be useless. You should read the license carefully to make sure it's suitable for what you want to do before continuing.
If you use MuPDF under the GNU AGPL license, we cannot guarantee you any support.
If you can't abide by the terms of the GNU AGPL, then you can obtain a commercial license from Artifex (mail sales#artifex.com with as many details of your project as possible and they'll talk to you about a customised licensing proposal). This frees you from all the onerous terms of the GNU AGPL.
If you are unable to comply with the GNU AGPL, or you are unwilling to pay for a commercial license, then you cannot use MuPDF.
Now, onto the actual question...
pdf_crypt.c implements the standard decryption handlers used in PDF. It sounds to me like you want to do something non-standard. As such, pdf_crypt.c will require some changes.
One technique would be to create a perfectly normal encrypted PDF file using a password. Your app can provide the password to MuPDF as it opens the file, and decryption will work seamlessly for you. The user need not even know that there is a password involved. In terms of coding this is the simplest possible way to go.
Another technique would be to do block based encryption of the file, and to decrypt blocks of the file as you load it - we have customers who do DRM in their products like this.
It's impossible to give you more information without knowing more about what you want to do.
If you wish to discuss this problem more, your best bet is probably to come to the #ghostscript irc channel (see mupdf.com for a link that will open this in your web browser).

C++ AES OpenSSL

I'm trying to read a string from a file encrypt it with AES and then save it to other file. Later I need to read the new file, decrypt and save the output to a new file again. The problem is some strange character are appearing.
int Crypt::__aesEncrypt(const unsigned char *msg, size_t msgLen, unsigned char **encMsg) {
EVP_CIPHER_CTX *aesEncryptCtx = (EVP_CIPHER_CTX*)malloc(sizeof(EVP_CIPHER_CTX));
EVP_CIPHER_CTX_init(aesEncryptCtx);
unsigned char *aesKey = (unsigned char*)malloc(AES_KEYLEN/8);
unsigned char *aesIV = (unsigned char*)malloc(AES_KEYLEN/8);
unsigned char *aesPass = (unsigned char*)malloc(AES_KEYLEN/8);
unsigned char *aesSalt = (unsigned char*)malloc(8);
if(RAND_bytes(aesPass, AES_KEYLEN/8) == 0) {
return FAILURE;
}
if(RAND_bytes(aesSalt, 8) == 0) {
return FAILURE;
}
if(EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha1(), aesSalt, aesPass, AES_KEYLEN/8, AES_ROUNDS, aesKey, aesIV) == 0) {
return FAILURE;
}
strncpy((char*)aesKey, (const char*)"B374A26A714904AAB374A26A714904AA", AES_KEYLEN/8);
strncpy((char*)aesIV, (const char*)"B374A26A714904AA", AES_KEYLEN/16);
size_t blockLen = 0;
size_t encMsgLen = 0;
*encMsg = (unsigned char*)malloc(msgLen + AES_BLOCK_SIZE);
if(encMsg == NULL) return FAILURE;
(*encMsg)[0] = '\0';
if(!EVP_EncryptInit_ex(aesEncryptCtx, EVP_aes_256_cbc(), NULL, aesKey, aesIV)) {
return FAILURE;
}
if(!EVP_EncryptUpdate(aesEncryptCtx, *encMsg, (int*)&blockLen, (unsigned char*)msg, msgLen)) {
return FAILURE;
}
encMsgLen += blockLen;
if(!EVP_EncryptFinal_ex(aesEncryptCtx, *encMsg + encMsgLen, (int*)&blockLen)) {
return FAILURE;
}
EVP_CIPHER_CTX_cleanup(aesEncryptCtx);
free(aesEncryptCtx);
free(aesKey);
free(aesIV);
return encMsgLen + blockLen;
}
int Crypt::__aesDecrypt(unsigned char *encMsg, size_t encMsgLen, char **decMsg) {
EVP_CIPHER_CTX *aesDecryptCtx = (EVP_CIPHER_CTX*)malloc(sizeof(EVP_CIPHER_CTX));
EVP_CIPHER_CTX_init(aesDecryptCtx);
unsigned char *aesKey;
unsigned char *aesIV;
aesKey = (unsigned char*)malloc(AES_KEYLEN/8);
aesIV = (unsigned char*)malloc(AES_KEYLEN/8);
unsigned char *aesPass = (unsigned char*)malloc(AES_KEYLEN/8);
unsigned char *aesSalt = (unsigned char*)malloc(8);
if(RAND_bytes(aesPass, AES_KEYLEN/8) == 0) {
return FAILURE;
}
if(RAND_bytes(aesSalt, 8) == 0) {
return FAILURE;
}
if(EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha1(), aesSalt, aesPass, AES_KEYLEN/8, AES_ROUNDS, aesKey, aesIV) == 0) {
return FAILURE;
}
strncpy((char*)aesKey, (const char*)"B374A26A714904AAB374A26A714904AA", AES_KEYLEN/8);
strncpy((char*)aesIV, (const char*)"B374A26A714904AA", AES_KEYLEN/16);
size_t decLen = 0;
size_t blockLen = 0;
*decMsg = (char*)malloc(encMsgLen);
if(*decMsg == NULL) return FAILURE;
if(!EVP_DecryptInit_ex(aesDecryptCtx, EVP_aes_256_cbc(), NULL, aesKey, aesIV)) {
return FAILURE;
}
if(!EVP_DecryptUpdate(aesDecryptCtx, (unsigned char*)*decMsg, (int*)&blockLen, encMsg, (int)encMsgLen)) {
return FAILURE;
}
decLen += blockLen;
if(!EVP_DecryptFinal_ex(aesDecryptCtx, (unsigned char*)*decMsg + decLen, (int*)&blockLen)) {
return FAILURE;
}
decLen += blockLen;
(*decMsg)[decLen] = '\0';
EVP_CIPHER_CTX_cleanup(aesDecryptCtx);
return encMsgLen;
}
Encrypting:
unsigned char *encMsg = NULL;
int size = __aesEncrypt((const unsigned char*)decrypted_string.c_str(), decrypted_string.size(), &encMsg);
return std::string(reinterpret_cast<const char*>(encMsg), size);
Decrypting:
char *decMsg = NULL;
int size = __aesDecrypt((unsigned char*)encrypted_string.c_str(), encrypted_string.size(), &decMsg);
return std::string(reinterpret_cast<const char*>(decMsg), size);
I can successfully crypt and decrypt, but some strange characters are appearing at the end of the encrypted file, they are like empty spaces:
AES is a block cypher. It takes blocks of 16 bytes, and encrypt them into a block of 16 byets. If you try to use it with data whose length is not a multiple of 16, padding (usually random data) is added to round it up to a multiple of 16 bytes. You need to manage the length of the data yourself.
Example:
int encryptHelper(const string& msg, ...)
{
uint32_t msgSize = msg.length();
newMsg.push_back((msgSize >> 0) & 0xFF);
newMsg.push_back((msgSize >> 8) & 0xFF);
newMsg.push_back((msgSize >> 16) & 0xFF);
newMsg.push_back((msgSize >> 24) & 0xFF);
string newMsg(reinterpret_cast<const char*>(&msgSize), sizeof(msgSize));
newMsg += msg;
return __aesEncrypt(newMsg.c_str(), newMsg.length(), ...);
}
int decryptHelper(const string& encrypted, ...)
{
string msg = ... whatever you are doing to decrypt
uint32_t actualSize = 0;
// remove signal first, then widen
actualSize |= static_cast<uint32_t>(static_cast<unsigned char>(msg[0])) << 0;
actualSize |= static_cast<uint32_t>(static_cast<unsigned char>(msg[1])) << 8;
actualSize |= static_cast<uint32_t>(static_cast<unsigned char>(msg[2])) << 16;
actualSize |= static_cast<uint32_t>(static_cast<unsigned char>(msg[3])) << 24;
string actualMsg = msg.substr(4, actualSize);
...
}
I didn't bother to write the exact code to call your functions because all that casting and memory leaking gave me nausea. Fill in the blanks.