I want to use a hybrid encryption scheme for file encryption. The scheme must use PKCS#1 v1.5 – which is vulerable to padding oracle attacks – because many smartcards (e.g. the OpenPGP Card) only support PKCS#1 v1.5.

So I came up with the following scheme (the RSA-keys are 4096-bit large, the symmetric algorithm is either AES-256-CTR or ChaCha-20 with 256bit keys and of course different keys for signing and encrypting are used. And of course I know that for stream-ciphers absolutely unique IVs must be used with the same key):


var privateSigningKey;
var publicEncryptionKey;

---   var encryptedBlock = encryptAsym(sessionKey, iV) + encryptSym(data + hash(data));
+++   var encryptedBlock = encryptAsym(sessionKey, iV) + encryptSym(data + sign(data));
var signatureBlock = sign(encryptedBlock);
var encryptedData = encryptedBlock + signatureBlock;

return encryptedData;


var trustedPublicKeys[];
var signatureBlock = encryptedData.getSignatureBlock();
var encryptedBlock = encryptedData.getEncryptedBlock();
var sender = "";

for each trustedPublicKey do {
    bool signatureValid = verifySignature(encryptedBlock, signatureBlock, trustedPublicKeys[i].getSigningKey();
    if (signatureValid) sender = trustedPublicKeys[i].getName();

if (sender == "") exit("Untrusted signature; stopping to prevent padding oracle attacks.");

var plainDataBlock = decryptHybrid(encryptedBlock);
---   var hash = plainDataBlock.getHash();
+++   var signature = plainDataBlock.getHash();
var plainData = plainDataBlock.getPlainData();

---   if (hash != hash(plainData)) exit("Decryption failed.");
+++   if (signature != sign(plainData)) exit("Decryption failed; untrusted signature.");

return plainData;

Can this scheme considered to be secure? Or are there any security issues or better alternatives?

I know that you usually don't roll your own crypto; but I cannot use the OpenPGP format because it doesn't prevent padding attacks (look at this question) and I don't know any better alternative.

Edit: Scheme updated

  • Is non-repudiation a goal?
    – user49075
    Feb 20, 2015 at 12:30
  • Well, authenticity is a goal; and non-repudiation is no harm. Feb 20, 2015 at 12:54

1 Answer 1


Your scheme does not protect against an adversary choosing the same SigningKey as an honest party. Even if that is not a problem, whether or not an adversary could falsely "get credit for"
a plaintext still depends on the details of PKCS#1 v1.5, since it would need to be infeasible
for an adversary to find a different malicious SigningKey for which is likely to accept a
mauled version of a message-signature pair from the honest sender's privateSigningKey.

Similarly, your scheme does not protect against an adversary choosing the same
publicEncryptionKey as an honest party. Even if those were not a problem, whether or not
your scheme provides authenticity would still depend on the details of PKCS#1 v1.5,
since it would need to be infeasible for an adversary to win the following game:

adversary receives an honestly-generated SigningKey and publicEncryptionKey
adversary outputs a plaintext data an alleged RSA public encryption key maliciousEncryptKey
sessionKey and iV are generated honestly

using maliciousEncryptKey,
encryptedBlock = encryptAsym(sessionKey, iV) + encryptSym(data + sign(data))

adversary wins if and only if plainDataBlock = data + sign(data)

I'm not aware of any "approved" alternatives. However, the majority of a good alternative is:

have encryptSym be some implementation of one-time authenticated encryption

let bracketed lists denote some way to unambiguously indicate all entries of the list
(If all but one of them have known length, then concatenation would work,
otherwise one could use something like prefixfree(x) || prefixfree(y) || z .)

let to_be_signed = [receiver's_name,receiver's_public_keys,data]

let encryptedBlock =
[encryptAsym(receiver's_public_encryption_key,sessionKey), encryptSym([sender's_name,sender's_public_keys,to_be_signed,sign(to_be_signed)]

omit signatureBlock, and just return encryptedBlock

If there's no need for encryptedBlock to hide sender's_name, then that and
sender's_public_keys can be made associated data instead of being encrypted.

If one is confident in the CCA-security of the public key encryption, then the part of this answer
before this sentence but after the horizontal line will almost work. (See this answer's last paragraph.)
Otherwise, one should choose distinct equal-length values a and b (the bits 0 and 1
in either order would work fine, but it might be more convenient to let them be whole bytes),
replace to_be_signed with a || [receiver's_name,receiver's_public_keys,data],
and let signatureBlock = sign ( b || encryptedBlock ) instead of omitting it.
Note that in this case, signatureBlock will usually be enough to deduce sender's_name,
which means there is usually very little point in having encryptedBlock hide that.

One also needs to make sure the bracketing operation and signature scheme are such that the lengths of their outputs do not leak anything more than necessary about information that should stay confidential
(since encryption can leak the length of the message). As a sufficient condition, those will hold if
{lengths of list representations depend only on the lengths of the entries and for honestly generated signature key-pairs, lengths of signatures depend only on the lengths of the corresponding messages}.
Also, if you want to allow for multi-receiver messages, then things are significantly trickier than
what I've described in this answer (though for reasons unrelated to the rest of this paragraph).

  • If I understand your point correctly, the problem is the following: Bob sends Alice a message. Eve intercepts the message and signs the encrypted block with her own signature and replaces Bob's signature. Because Alice trusts also Eve's key, Alice thinks, that the message is from Eve. Also could Eve alter the message and sign the altered message with her valid signing key, the alteration would only be noticed if the decryption fails? If I'm correct, would my edit remove this flaw? Feb 20, 2015 at 14:34
  • Eve certainly would've been able to do that before your edit (which I didn't realize). My edited-11-minutes-ago answer's top paragraph gives a vague sketch of how Eve might still do that. For an independent reason, the signature scheme must be such that for an honestly-generated key-pair, given the SigningKey and access to signatures on chosen messages, (... continued)
    – user49075
    Feb 20, 2015 at 15:46
  • (continued ...) the length of signatures computationally does not reveal anything about the signed messages other than their length. (However, having fixed-length signatures is nice anyway, since it makes .getSignature() easy, which is why that requirement is usually not mentioned.) Other than those, I haven't yet found any way for Eve to still do that.
    – user49075
    Feb 20, 2015 at 15:47
  • If I understood you right, you assume that either Eve has gathered access to a private signingKey or Eve is able to generate a signingKey which is able to forge messages? The last should be impossible with RSA. And the security of the signatures depends of course of PKCS#1 v1.5, but it's signature-scheme which is considered secure. The issue in PKCS#1 v1.5 addresses the encryption-scheme of PKCS#1 v1.5 not its signature-scheme. Please correct me if I'm wrong :) Feb 20, 2015 at 16:06
  • Actually, I forgot to consider the case in which "Eve has gathered access to a" privateSigningKey. In that case, Eve could easily exploit the malleability of stream ciphers.
    – user49075
    Feb 20, 2015 at 16:12

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .