diff options
Diffstat (limited to 'README.md')
| -rw-r--r-- | README.md | 133 |
1 files changed, 114 insertions, 19 deletions
| @@ -69,12 +69,97 @@ for processes that were not spawned by that same *etoolkit* session. | |||
| 69 | # add sgs' custom repository using app-eselect/eselect-repository | 69 | # add sgs' custom repository using app-eselect/eselect-repository |
| 70 | eselect repository add sgs | 70 | eselect repository add sgs |
| 71 | 71 | ||
| 72 | # ... or using layman (obsolete) | ||
| 73 | layman -a sgs | ||
| 74 | |||
| 75 | emerge dev-python/etoolkit | 72 | emerge dev-python/etoolkit |
| 76 | ``` | 73 | ``` |
| 77 | 74 | ||
| 75 | ## Encryption & decryption scheme | ||
| 76 | |||
| 77 | The etoolkit encryption format is currently at version 2. | ||
| 78 | Encrypted values start with *enc-val$2$*. | ||
| 79 | |||
| 80 | This new version introduces padding for values that are shorter than 32 bytes. | ||
| 81 | The idea behind padding is to generate (32 - value length) random bytes and | ||
| 82 | append them to the original value. | ||
| 83 | That prevents a potential attacker from knowing the length of the encrypted | ||
| 84 | short value (f.i. password, PIN number, username... etc). | ||
| 85 | |||
| 86 | Values encrypted in the old format (*enc-val$1$*) can still be decrypted | ||
| 87 | seamlessly. | ||
| 88 | |||
| 89 | Authenticated encryption with associated data (AEAD) is implemented using | ||
| 90 | AES-GCM. | ||
| 91 | |||
| 92 | |||
| 93 | ### Encryption | ||
| 94 | |||
| 95 | Input: | ||
| 96 | |||
| 97 | - plain-text value to be encrypted (P) | ||
| 98 | |||
| 99 | - plain-text master-password used for key derivation (M) | ||
| 100 | |||
| 101 | |||
| 102 | Output: | ||
| 103 | |||
| 104 | - an encrypted value digest (base64) (B) | ||
| 105 | |||
| 106 | |||
| 107 | Operation: | ||
| 108 | |||
| 109 | - generate 32 bytes of random data to be used as a salt (S) | ||
| 110 | |||
| 111 | - derive a 32 bytes key (K): K = scrypt(M, S, n=2**14, r=8, p=1) | ||
| 112 | |||
| 113 | - use the first 12 bytes of S as nonce (NONCE) | ||
| 114 | |||
| 115 | - calculate the padding length (L) as 32 - length of P, if P < 32, 0 otherwise | ||
| 116 | |||
| 117 | - set the padding length bytes (N) (2bytes) to "%02d", if L > 0, "-1" otherwise | ||
| 118 | |||
| 119 | - generate L bytes of random data to be used for padding (D) | ||
| 120 | |||
| 121 | - encrypt and auth. P, auth.only S (E): E = AES_GCM_ENC(K, NONCE, N + P + D, S) | ||
| 122 | |||
| 123 | - encrypted value digest (B) = enc-val$2$:BASE64_ENCODE(S)$BASE64_ENCODE(E) | ||
| 124 | |||
| 125 | example: | ||
| 126 | enc-val$2$uYpZM1VfAGq0CDZL2duITs076CQj+hIFEgx+F4mn80o=$UWP5YeRsh5/2vZ2J1UOS+BJti73Kbp6C1pJmCo8hFSujpe35X/XpzBegJJpo86AiCsNsUS6B6JM= | ||
| 127 | |||
| 128 | |||
| 129 | ### Decryption | ||
| 130 | |||
| 131 | Input: | ||
| 132 | |||
| 133 | - encrypted value digest (base64) (B) | ||
| 134 | |||
| 135 | - plain-text master-password used for key derivation (M) | ||
| 136 | |||
| 137 | |||
| 138 | Output: | ||
| 139 | |||
| 140 | - plain-text password (P) | ||
| 141 | |||
| 142 | |||
| 143 | Operation: | ||
| 144 | |||
| 145 | - remove the prefix (enc-val$2$) from B and split the remaining value by '$' | ||
| 146 | |||
| 147 | - base64-decode the salt (S): S = BASE64_DECODE(B1) | ||
| 148 | |||
| 149 | - base64-decode the rest of the data (E): E = BASE64_DECODE(B2) | ||
| 150 | |||
| 151 | - derive a 32 bytes key (K): K = scrypt(M, S, n=2**14, r=8, p=1) | ||
| 152 | |||
| 153 | - use the first 12 bytes of S as nonce (NONCE) | ||
| 154 | |||
| 155 | - decrypt the encrypted data (D): D = AES_GCM_DECRYPT(K, NONCE, E, S) | ||
| 156 | |||
| 157 | - fetch the first 2 bytes (padding length bytes) (N): N = D[0 : 2] | ||
| 158 | |||
| 159 | - calculate the padding length (L): L = INT(N) if N != "-1", 0 otherwise | ||
| 160 | |||
| 161 | - fetch the plain-text (P): P = D[2 : -L] if L != 0, D[2 :] otherwise | ||
| 162 | |||
| 78 | 163 | ||
| 79 | ## Setup and examples | 164 | ## Setup and examples |
| 80 | 165 | ||
| @@ -201,6 +286,17 @@ One can also spawn a different process than an interactive shell by using the | |||
| 201 | etoolkit --spawn /bin/othershell <instance-name> | 286 | etoolkit --spawn /bin/othershell <instance-name> |
| 202 | ``` | 287 | ``` |
| 203 | 288 | ||
| 289 | It is possible to re-encrypt all encrypted values in a specific instance or in | ||
| 290 | all defined instances either by using the same or a new master password. | ||
| 291 | |||
| 292 | ```bash | ||
| 293 | etoolkit --reencrypt all | ||
| 294 | ``` | ||
| 295 | |||
| 296 | will prompt for the current master password, then for a new master password | ||
| 297 | (with confirmation) and finally the new config file (if "all") or instance | ||
| 298 | contents will be displayed. | ||
| 299 | |||
| 204 | Contact the author for questions and suggestions! :) | 300 | Contact the author for questions and suggestions! :) |
| 205 | 301 | ||
| 206 | 302 | ||
| @@ -223,11 +319,11 @@ or the *instances* structure being loaded from a diferent configuration file | |||
| 223 | 319 | ||
| 224 | 320 | ||
| 225 | # using some static methods in order to create encrypted values | 321 | # using some static methods in order to create encrypted values |
| 226 | etoolkit.EtoolkitInstance.encrypt('the very secret passwd', 'secret1') | 322 | etoolkit.EtoolkitInstance.encrypt('The very secret passwd', 'secret1') |
| 227 | # Out: 'enc-val$1$Y/TBb1F3siHTw6qZg9ERzZfA8PLPf2CwGSQLpu9jYWw=$FT5tS9o+ABvsxogIXpJim16Gz5SVtV8=' | 323 | # Out: 'enc-val$2$NDdp6WMbX7gdEyzGM5nI4jhyer4XL+BoQwAHtL2CXHw=$+Pztn1pfaXKjPpem5PIQrCNxR9pyE6zqgSoGg9qXvmhH6VsNQvUTmiaOvUFl35EbiYE=' |
| 228 | 324 | ||
| 229 | etoolkit.EtoolkitInstance.encrypt('the very secret passwd', 'secret2') | 325 | etoolkit.EtoolkitInstance.encrypt('The very secret passwd', 'secret2') |
| 230 | # Out: 'enc-val$1$vIBcoCNiYrsDLtF41uLuSEnppBjhliD0B8jwcBJcj/c=$KwOGe/y1dlxktDaCnJPIVNuaQ4Q7yNo=' | 326 | # Out: 'enc-val$2$H953GxW+qrYXIp+I97lJBmG1gv89wxcfmTu7PEpZzjE=$Tb3F8/izDbHAMklpIjYk73JAiav+w8ZhrMsO93FlQjGh4MTChjp2Yen5BxSBOWLvCD4=' |
| 231 | 327 | ||
| 232 | 328 | ||
| 233 | # The encrypted values will be used in our configuration structure | 329 | # The encrypted values will be used in our configuration structure |
| @@ -237,34 +333,33 @@ or the *instances* structure being loaded from a diferent configuration file | |||
| 237 | }, | 333 | }, |
| 238 | "instances": { | 334 | "instances": { |
| 239 | "_default": { | 335 | "_default": { |
| 240 | "ETOOLKIT_PROMPT": "(%i)", | 336 | "ETOOLKIT_PROMPT": "(%i)", |
| 241 | "PYTHONPATH": "/home/user/%i/python", | 337 | "ETOOLKIT_SENSITIVE": ["DB_CONNECTION", "ETOOLKIT_TEST_PASSWORD"] |
| 242 | }, | 338 | }, |
| 243 | "dev": { | 339 | "dev": { |
| 244 | "ETOOLKIT_PARENT": "_default", | 340 | "ETOOLKIT_PARENT": "_default", |
| 245 | "PYTHONPATH": "%p:/home/user/%i/.pythonpath", | 341 | "PYTHONPATH": ":/home/user/.pythonpath", |
| 342 | "DB_CONNECTION": "enc-val$2$RAgDei59tUvDAkrBmxROqRaV/NxNFEI2eJIOP7sG/b8=$yse7zawHCzQCU31sZj4oJYLGonz1M7oqHqCilXLHkywa9nMPALypmVzi3QekekYuLeb5XVTmmp84NHoPn1M052otoRHSp+TMPsqBPRabfriIKEK4XQ==" | ||
| 246 | }, | 343 | }, |
| 247 | "secret": { | 344 | "secret": { |
| 248 | "ETOOLKIT_PARENT": "_default", | 345 | "ETOOLKIT_PARENT": "_default", |
| 249 | "ETOOLKIT_SENSITIVE": ["PASSWORD"], | ||
| 250 | "GNUPGHOME": "%h/private/.gnupg", | 346 | "GNUPGHOME": "%h/private/.gnupg", |
| 251 | "PASSWORD": "enc-val$1$vIBcoCNiYrsDLtF41uLuSEnppBjhliD0B8jwcBJcj/c=$KwOGe/y1dlxktDaCnJPIVNuaQ4Q7yNo=" | 347 | "ETOOLKIT_TEST_PASSWORD": "enc-val$2$RCSZqq9pWrRDoCVYVHopyu1LzaJGfv8roVviqrLTBxM=$+YYrZbwTBuG0Pl+WMQrvxLUtq5j8qYuQqzoIwgoGt7AaWZCJz+E7qoDeg3wke70ST8U=" |
| 252 | } | 348 | } |
| 253 | } | 349 | } |
| 254 | } | 350 | } |
| 255 | 351 | ||
| 256 | 352 | secret_instance = etoolkit.EtoolkitInstance('secret', instances) | |
| 257 | dev_instance = etoolkit.EtoolkitInstance('dev', instances) | ||
| 258 | 353 | ||
| 259 | # fetch the variables before the processing stage (calling get_environ()) | 354 | # fetch the variables before the processing stage (calling get_environ()) |
| 260 | # since raw_env_variables is a dict, it can be modified (f.i. .update()) | 355 | # since raw_env_variables is a dict, it can be modified (f.i. .update()) |
| 261 | dev_instance.raw_env_variables | 356 | secret_instance.raw_env_variables |
| 262 | 357 | ||
| 263 | dev_instance.master_password = 'the very secret passwd' # or perhaps using getpass | 358 | secret_instance.master_password = 'The very secret passwd' # or perhaps using getpass |
| 264 | env_vars = dev_instance.get_env() | 359 | env_vars = secret_instance.get_environ() |
| 265 | print(env_vars['PASSWORD']) # outputs: 'secret2' | 360 | print(env_vars['ETOOLKIT_TEST_PASSWORD']) # outputs: 'secret1' |
| 266 | 361 | ||
| 267 | inst.dump_env(env_vars) # prints all values, with the exception of 'PASSWORD' | 362 | secret_instance.env_to_str(env_vars) # prints all values, with the exception of 'ETOOLKIT_TEST_PASSWORD' |
| 268 | 363 | ||
| 269 | # set the env. variables. | 364 | # set the env. variables. |
| 270 | os.environ.update(env_vars) | 365 | os.environ.update(env_vars) |
