Skip to content

Encrypted data reveal the length of the clear text data #815

Description

@xformerfhs

Hello, everybody,

I post this issue after having contacted Julien Vehent. He suggested to open an issue here so it can be discussed. So I post it here in the hope to contribute to the usefulness and security of this great software.

Problem description

The length of the encrypted form of the data is always the same length as the clear text data. So it is possible to guess the data value from it’s length.

Let us take this yaml file as an example:

strings:
- a
- aB
- aBc
- aBcD
- aBcDe
numbers:
- 1
- 12
- 123
- 1.2
- 1.2300
booleans:
- true
- false
- y
- n
- on
- off

This is converted to something like this by sops:

field:
- ENC[AES256_GCM,data:Qw==,iv:3Db00Y2V3vELD+cxyeAzaI0WdZOOqw7P3SrIphYlkM8=,tag:Rm1hVD4J7Wcyteh2pUXqQw==,type:str]
- ENC[AES256_GCM,data:hlU=,iv:RStn69CRX7uddn7rKHu0YYYNvsSaxPp0bqgsuamdAIM=,tag:BjVgphl+yqd49lkGFvVtAw==,type:str]
- ENC[AES256_GCM,data:UXxu,iv:a4ufQdVF1dwH1m+wZRdd2tYIZaXKRj/QhzyJ1HDu7dg=,tag:xbUz2UxdVOV/GuDKkjTXdA==,type:str]
- ENC[AES256_GCM,data:HA1E1Q==,iv:6Y7pxNWY4jZn5OLfa4Sd/DQzo9ye2ubM1u2e5IQN9I0=,tag:a1WgmqUhJKvqo/qKbWvTiQ==,type:str]
- ENC[AES256_GCM,data:PYfdMqo=,iv:X+Z1DdUV7A+4p8Yj8zFYBhJtNJFLm1mqOmHevMcDxn0=,tag:l97d0/y8b/uBM50DLZ/TzA==,type:str]
numbers:
- ENC[AES256_GCM,data:IQ==,iv:AFshGBvOry/iUaPNm9ZgFrkb03AevzERJhEH8AiCfcQ=,tag:sILDbauS1RqIBCQM6XWArQ==,type:int]
- ENC[AES256_GCM,data:tSI=,iv:vvnAkSw69eQL64bmsZpjrCIh16qI13esc3tLsLGPlj0=,tag:Pf3Y/m3xP81fSVrcMez0eg==,type:int]
- ENC[AES256_GCM,data:rBeB,iv:xgzYmW9i6y61Udt226ooTx/vzcOQFtgIAVyomUNGix0=,tag:h8yHZasQGHa3AY4VG1J0ZA==,type:int]
- ENC[AES256_GCM,data:rNTf,iv:EzN8AQ4T63J0YjV+y2y6ViOe9M2KrERICpbEsTU/1io=,tag:Thftkoq//92XyzS3ZmKBgg==,type:float]
- ENC[AES256_GCM,data:Gvzw2A==,iv:OrdWXplruWs4BG2DduSL4Ld72Teao62WivMWISkG9+E=,tag:6XF2e3HNwyV3YB7AyPw2Rg==,type:float]
booleans:
- ENC[AES256_GCM,data:kU1bxA==,iv:h+B5QbhktBIP2gSoyPlYtFq+/LDlvRP5ndXuL8GN6xY=,tag:PIV5v63hDg4wPnEsFikeAA==,type:bool]
- ENC[AES256_GCM,data:yRRf3Kc=,iv:+kJKCNe2BHFU0zEK4IJoDUk+/VWXgiddyYxhFlqNnZA=,tag:dXw+8mSWKtTJd4F5VMHp/Q==,type:bool]
- ENC[AES256_GCM,data:kU1bxA==,iv:h+B5QbhktBIP2gSoyPlYtFq+/LDlvRP5ndXuL8GN6xY=,tag:PIV5v63hDg4wPnEsFikeAA==,type:bool]
- ENC[AES256_GCM,data:yRRf3Kc=,iv:+kJKCNe2BHFU0zEK4IJoDUk+/VWXgiddyYxhFlqNnZA=,tag:dXw+8mSWKtTJd4F5VMHp/Q==,type:bool]
- ENC[AES256_GCM,data:kU1bxA==,iv:h+B5QbhktBIP2gSoyPlYtFq+/LDlvRP5ndXuL8GN6xY=,tag:PIV5v63hDg4wPnEsFikeAA==,type:bool]
- ENC[AES256_GCM,data:yRRf3Kc=,iv:+kJKCNe2BHFU0zEK4IJoDUk+/VWXgiddyYxhFlqNnZA=,tag:dXw+8mSWKtTJd4F5VMHp/Q==,type:bool]

When we look at the encrypted data (the values after the data: tag) we see the following:

Type str:

Clear text Clear text length Encrypted bytes Encrypted length
a 1 0x43 1
aB 2 0x8655 2
aBc 3 0x517c6e 3
aBcD 4 0x1c0d44d5 4
aBcDe 5 0x3d87dd32aa 5

Type int:

Clear text Clear text length Encrypted bytes Encrypted length
1 1 0x21 1
12 2 0xb522 2
123 3 0xac1781 3

Type float:

Clear text Clear text length Encrypted bytes Encrypted length
1.2 3 0xacd4df 3
1.2300 6 (true length is 4) 0x1afcf0d8 4

Type boolean:

Clear text Clear text length Encrypted bytes Encrypted length
true 4 0x914d5bc4 4
false 5 0xc9145fdca7 5

There are two observations:

  1. The data is "canonicalized" before being encrypted
    1. This is most easily seen for the boolean values where all variants are mapped to true and false
    2. All the other types are processed, as well, as trailing zeroes (for int and float) or blanks (for all types) are removed before the data is encrypted
  2. The length of the encrypted bytes is always the same as the length of the "canonicalized" clear text

What is the implication?

An adversary does not know the content of a str, int or float but in the current implementation of sops he knows the clear text length from the encrypted lengths and this is a valuable information. If the clear text is short, like e.g. up to 5 characters, he can simply guess or brute force the clear text value. Having the length of the clear text limits the number of tries he has perform. If the length of the clear text data is long enough (whatever "long enough" means) this does not reduce the security as there are just to many possibilities to try.

Numbers tend to be short and so can easily be guessed by just knowing their length. So this is a problem for numbers and short strings.

A special case are boolean values. They can only have 2 different values, so it is trivial to "guess" the value. The adversary will have the correct value after at most two tries. All he needs to know is the type.

What to do about it?

I propose to conceal the true length of the data by prepending and appending random data to them before they are encrypted. There should be 0-m data prepended and 0-n data appended. The lengths of these "blindings" should be stored with the data.

Here is an example of how that could be done:

  1. Clear text: data
  2. Clear text as bytes: 0x64617461
  3. Prepend e.g. 3 random bytes and append e.g. 4 random bytes: 0x168ffa64617461a29fbc5d
  4. Append the lengths of the prepended and appended bytes: 0x168ffa64617461a29fbc5d0304

So the data that is finally encrypted is 0x168ffa64617461a29fbc5d0304, instead of 0x64617461.

On decryption the blinding lengths are read from the end of the decrypted bytes. With this information the blinding can be removed and the data restored.

This is just an example. There may be better ways to do this.

There should just be a method to conceal the length of the clear text.

Of course one needs some means to differentiate between values that use blinding and others that do not.

Regarding the boolean values I think it does not make sense to "blind" them like in the example above as there are only two possible clear text values. The only means to protect them would be to conceal the type and treat them in a special way.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions