10 of 13
Recommend an authenticated encryption mode
  • 111218
  • 9
  • 239
  • 475

Is it secure to encrypt with AES CFB mode a plaintext for which the first bytes are known (e.g. %PDF-1.5 for a PDF document)

Yes. Known (and even chosen) plaintext is a standard assumption in any moder cipher, including AES-CFB (and CTR, CBC, OFB...). It does not "give too much information to an attacker", and it is not "bad to encrypt documents with known headers with AES".

True, having a known plaintext at start to allow a test of the password makes password search a little simpler. But only a little, and we customarily ignore that little. One reason (stated in the question) is that actual plaintext typically contains recognizable sequences. And when not, it is typically compressible, when the incorrect plaintext obtained for the wrong password is not, which allows automated recognition of having the correct password anyway.

Another problem is that CFB is weak at detecting message alteration. Using a modern, authenticated encryption mode such as AES-GCM would solve that. It turns out this is readily available in Crypto.Cipher.

However, there is a different, serious problem with the code !

It has poor security because the Crypto.Hash.SHA256.new(b'password1') part of it builds the AES key from a password using a standard cryptographic hash (SHA-256). That conversion from password to key is fast, and that allows to test passwords at high speed. This makes the system very vulnerable to password crackers (which essentially test plausible passwords at high speed, including short ones, and those from or inspired from a dictionary of common passwords/words).

I use a random Initialization Vector, so doesn't this help (against the above problem)?

No, because that does not appreciably slow down testing a password.

That issue has plagued password-based encryption since the origin of that, and progress in computing (faster CPUs, GPUs, FPGAs, ASICs..) directly worsen it, to the point that nowadays, very practically, if users can memorize a password, and a standard hash is used to turn it into a key, then the system is very weak (much to the pleasure of Three Letters Agencies and more casual/greedy attackers, which routinely use password crackers, and succeed).

In the 1990 we started to get iterated password-to-key derivation functions, like PBKDF2 of RFC 2898, which essentially slow down conversion from password to key by requiring many hashes. Initially "many" was at least a thousand, but nowadays we'd need hundred thousands just to keep with the progress of technology. Until about 2015, that was the industrial state of the art (what your mobile phone or computer used).

We are slowly moving to memory-intensive (or memory-hard) password-to-key functions. The pioneer was bcrypt (which almost accidentally required sizable memory), then scrypt (theorizing the use of large memory and multiple CPUs when available). The Password Hashing Competition gave us Argon2 (which unfortunately fails to catch AFAIK, perhaps because it's complex and has many options). There is also Balloon (which does not catch either, perhaps because it was not in the PHC).

In any case, it is nowadays inadequate and grossly incompetent to turn a password into an encryption key using anything lesser than an iterated hash with many thousand iterations; and it is highly recommendable to additionally make that process require sizable memory.

Could you include an example of code with which you would replace AES.new(SHA256.new(b'password1').digest(), AES.MODE_CFB, iv).encrypt(p) to make use of another hash?

The bad news (beside crypto.SE not being for code recommendations) is that we need another kind of hash. One variously called entropy-stretching key derivation function, password-based hash, or some mix of that. It must be purposely as slow as possible for the attacker, yet fast enough for the application. Also, it will have a salt input, and it is a good idea to feed that with the IV, of perhaps server+user ID (this was missing in an earlier version of this answer).

We have to choose between several evils:

  1. Using an entropy-stretching key derivation function readily available in the package that we use. That's by far the easiest: the Crypto package has PBKDF2. Be sure to up the count parameter as much as bearable: the default 1000 was already insecure 20 years ago.
    Concretely, the change would be from SHA256.new(b'password1').digest() to something on the tune of (not tested thus probably wrong, and at least lacking import)
      PBKDF2(password=b'password1', salt=iv, dkLen=32, count=1000000)
    perhaps with the following additional parameter inserted before the last parenthesis in order to avoid the deprecated SHA-1, if performance issues do not make that too suboptimal thus insecure (see that issue in 3.)
      , prf=lambda p,s: HMAC.new(p,s,SHA512).digest()
    Main and serious problem is, the Crypto package does not come with any memory-hard entropy-stretching key derivation function. PBKDF2 uses little memory, making it very suboptimal from a security standpoint. And it is one of the entropy-stretching key derivation functions that attackers are the most proficient at.

  2. A Python binding to a standard memory-hard entropy-stretching key derivation function, like scrypt or Argon2, e.g. this or this.
    Problem is that installation is horribly platform-dependent, and that it is hard to assess the quality/security of the outcome (I did not even try). On the other hand, this has the potential to give the best security.

  3. Something doing the same computation as 2. but coded in pure Python, for portability.
    Problem is, that will be very appreciably slower (perhaps 10 or 100 times, possibly worse); thus in practice less secure (by the same factor) because the security level is directly limited by how much time we are willing to wait for the password-to-key transformation.

  4. Something pure Python implementing a custom memory-hard entropy-stretching key derivation function designed to not be too much penalized by being pure Python; which is a lot of work.

  • 111218
  • 9
  • 239
  • 475