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:
- The data is "canonicalized" before being encrypted
- This is most easily seen for the
boolean values where all variants are mapped to true and false
- 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
- 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:
- Clear text:
data
- Clear text as bytes:
0x64617461
- Prepend e.g. 3 random bytes and append e.g. 4 random bytes:
0x168ffa64617461a29fbc5d
- 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.
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:
This is converted to something like this by sops:
When we look at the encrypted data (the values after the
data:tag) we see the following:Type
str:0x430x86550x517c6e0x1c0d44d50x3d87dd32aaType
int:0x210xb5220xac1781Type
float:0xacd4df0x1afcf0d8Type
boolean:0x914d5bc40xc9145fdca7There are two observations:
booleanvalues where all variants are mapped totrueandfalseintandfloat) or blanks (for all types) are removed before the data is encryptedWhat is the implication?
An adversary does not know the content of a
str,intorfloatbut 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
booleanvalues. 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 thetype.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-mdata prepended and0-ndata appended. The lengths of these "blindings" should be stored with the data.Here is an example of how that could be done:
data0x646174610x168ffa64617461a29fbc5d0x168ffa64617461a29fbc5d0304So the data that is finally encrypted is
0x168ffa64617461a29fbc5d0304, instead of0x64617461.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
booleanvalues 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.