1#!/usr/bin/env python 2 3""" 4<Program Name> 5 pyca_crypto_keys.py 6 7<Author> 8 Vladimir Diaz <vladimir.v.diaz@gmail.com> 9 10<Started> 11 June 3, 2015. 12 13<Copyright> 14 See LICENSE for licensing information. 15 16<Purpose> 17 The goal of this module is to support public-key and general-purpose 18 cryptography through the pyca/cryptography (available as 'cryptography' on 19 pypi) library. 20 21 The RSA-related functions provided include: 22 generate_rsa_public_and_private() 23 create_rsa_signature() 24 verify_rsa_signature() 25 create_rsa_encrypted_pem() 26 create_rsa_public_and_private_from_pem() 27 28 The general-purpose functions include: 29 encrypt_key() 30 decrypt_key() 31 32 pyca/cryptography performs the actual cryptographic operations and the 33 functions listed above can be viewed as the easy-to-use public interface. 34 35 https://pypi.python.org/pypi/cryptography/ 36 https://github.com/pyca/cryptography 37 38 https://en.wikipedia.org/wiki/RSA_(algorithm) 39 https://en.wikipedia.org/wiki/Advanced_Encryption_Standard 40 https://en.wikipedia.org/wiki/PBKDF 41 http://en.wikipedia.org/wiki/Scrypt 42 43 TUF key files are encrypted with the AES-256-CTR-Mode symmetric key 44 algorithm. User passwords are strengthened with PBKDF2, currently set to 45 100,000 passphrase iterations. The previous evpy implementation used 1,000 46 iterations. 47 48 PEM-encrypted RSA key files use the Triple Data Encryption Algorithm (3DES), 49 and Cipher-block chaining (CBC) for the mode of operation. Password-Based 50 Key Derivation Function 1 (PBKF1) + MD5. 51 """ 52 53# Help with Python 3 compatibility, where the print statement is a function, an 54# implicit relative import is invalid, and the '/' operator performs true 55# division. Example: print 'hello world' raises a 'SyntaxError' exception. 56from __future__ import print_function 57from __future__ import absolute_import 58from __future__ import division 59from __future__ import unicode_literals 60 61import os 62import binascii 63import json 64 65# Import pyca/cryptography routines needed to generate and load cryptographic 66# keys in PEM format. 67from cryptography.hazmat.primitives import serialization 68from cryptography.hazmat.backends.interfaces import PEMSerializationBackend 69from cryptography.hazmat.primitives.serialization import load_pem_private_key 70from cryptography.hazmat.backends import default_backend 71 72# Import Exception classes need to catch pyca/cryptography exceptions. 73import cryptography.exceptions 74 75# 'cryptography.hazmat.primitives.asymmetric' (i.e., pyca/cryptography's 76# public-key cryptography modules) supports algorithms like the Digital 77# Signature Algorithm (DSA) and the ECDSA (Elliptic Curve Digital Signature 78# Algorithm) encryption system. The 'rsa' module module is needed here to 79# generate RSA keys and PS 80from cryptography.hazmat.primitives.asymmetric import rsa 81 82# pyca/cryptography requires hash objects to generate PKCS#1 PSS 83# signatures (i.e., padding.PSS). The 'hmac' module is needed to verify 84# ciphertexts in encrypted key files. 85from cryptography.hazmat.primitives import hashes 86from cryptography.hazmat.primitives import hmac 87 88# RSA's probabilistic signature scheme with appendix (RSASSA-PSS). 89# PKCS#1 v1.5 is available for compatibility with existing applications, but 90# RSASSA-PSS is encouraged for newer applications. RSASSA-PSS generates 91# a random salt to ensure the signature generated is probabilistic rather than 92# deterministic (e.g., PKCS#1 v1.5). 93# http://en.wikipedia.org/wiki/RSA-PSS#Schemes 94# https://tools.ietf.org/html/rfc3447#section-8.1 95# The 'padding' module is needed for PSS signatures. 96from cryptography.hazmat.primitives.asymmetric import padding 97 98# Import pyca/cryptography's Key Derivation Function (KDF) module. 99# 'securesystemslib.keys.py' needs this module to derive a secret key according 100# to the Password-Based Key Derivation Function 2 specification. The derived 101# key is used as the symmetric key to encrypt TUF key information. 102# PKCS#5 v2.0 PBKDF2 specification: http://tools.ietf.org/html/rfc2898#section-5.2 103from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC 104 105# pyca/cryptography's AES implementation available in 'ciphers.Cipher. and 106# 'ciphers.algorithms'. AES is a symmetric key algorithm that operates on 107# fixed block sizes of 128-bits. 108# https://en.wikipedia.org/wiki/Advanced_Encryption_Standard 109from cryptography.hazmat.primitives.ciphers import Cipher, algorithms 110 111# The mode of operation is presently set to CTR (CounTeR Mode) for symmetric 112# block encryption (AES-256, where the symmetric key is 256 bits). 'modes' can 113# be used as an argument to 'ciphers.Cipher' to specify the mode of operation 114# for the block cipher. The initial random block, or initialization vector 115# (IV), can be set to begin the process of incrementing the 128-bit blocks and 116# allowing the AES algorithm to perform cipher block operations on them. 117from cryptography.hazmat.primitives.ciphers import modes 118 119import securesystemslib.exceptions 120import securesystemslib.formats 121import securesystemslib.util 122 123# Extract/reference the cryptography library settings. 124import securesystemslib.settings 125 126# Recommended RSA key sizes: 127# http://www.emc.com/emc-plus/rsa-labs/historical/twirl-and-rsa-key-size.htm#table1 128# According to the document above, revised May 6, 2003, RSA keys of size 3072 129# provide security through 2031 and beyond. 130_DEFAULT_RSA_KEY_BITS = 3072 131 132# The delimiter symbol used to separate the different sections of encrypted 133# files (i.e., salt, iterations, hmac, IV, ciphertext). This delimiter is 134# arbitrarily chosen and should not occur in the hexadecimal representations of 135# the fields it is separating. 136_ENCRYPTION_DELIMITER = '@@@@' 137 138# AES key size. Default key size = 32 bytes = AES-256. 139_AES_KEY_SIZE = 32 140 141# Default salt size, in bytes. A 128-bit salt (i.e., a random sequence of data 142# to protect against attacks that use precomputed rainbow tables to crack 143# password hashes) is generated for PBKDF2. 144_SALT_SIZE = 16 145 146# Default PBKDF2 passphrase iterations. The current "good enough" number 147# of passphrase iterations. We recommend that important keys, such as root, 148# be kept offline. 'settings.PBKDF2_ITERATIONS' should increase as CPU 149# speeds increase, set here at 100,000 iterations by default (in 2013). 150# Repository maintainers may opt to modify the default setting according to 151# their security needs and computational restrictions. A strong user password 152# is still important. Modifying the number of iterations will result in a new 153# derived key+PBDKF2 combination if the key is loaded and re-saved, overriding 154# any previous iteration setting used by the old '<keyid>.key'. 155# https://en.wikipedia.org/wiki/PBKDF2 156_PBKDF2_ITERATIONS = securesystemslib.settings.PBKDF2_ITERATIONS 157 158 159 160def generate_rsa_public_and_private(bits=_DEFAULT_RSA_KEY_BITS): 161 """ 162 <Purpose> 163 Generate public and private RSA keys with modulus length 'bits'. The 164 public and private keys returned conform to 165 'securesystemslib.formats.PEMRSA_SCHEMA' and have the form: 166 167 '-----BEGIN RSA PUBLIC KEY----- ...' 168 169 or 170 171 '-----BEGIN RSA PRIVATE KEY----- ...' 172 173 The public and private keys are returned as strings in PEM format. 174 175 'generate_rsa_public_and_private()' enforces a minimum key size of 2048 176 bits. If 'bits' is unspecified, a 3072-bit RSA key is generated, which is 177 the key size recommended by TUF. 178 179 >>> public, private = generate_rsa_public_and_private(2048) 180 >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(public) 181 True 182 >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(private) 183 True 184 185 <Arguments> 186 bits: 187 The key size, or key length, of the RSA key. 'bits' must be 2048, or 188 greater. 'bits' defaults to 3072 if not specified. 189 190 <Exceptions> 191 securesystemslib.exceptions.FormatError, if 'bits' does not contain the 192 correct format. 193 194 <Side Effects> 195 The RSA keys are generated from pyca/cryptography's 196 rsa.generate_private_key() function. 197 198 <Returns> 199 A (public, private) tuple containing the RSA keys in PEM format. 200 """ 201 202 # Does 'bits' have the correct format? 203 # This check will ensure 'bits' conforms to 204 # 'securesystemslib.formats.RSAKEYBITS_SCHEMA'. 'bits' must be an integer 205 # object, with a minimum value of 2048. Raise 206 # 'securesystemslib.exceptions.FormatError' if the check fails. 207 securesystemslib.formats.RSAKEYBITS_SCHEMA.check_match(bits) 208 209 # Generate the public and private RSA keys. The pyca/cryptography 'rsa' 210 # module performs the actual key generation. The 'bits' argument is used, 211 # and a 2048-bit minimum is enforced by 212 # securesystemslib.formats.RSAKEYBITS_SCHEMA.check_match(). 213 private_key = rsa.generate_private_key(public_exponent=65537, key_size=bits, 214 backend=default_backend()) 215 216 # Extract the public & private halves of the RSA key and generate their 217 # PEM-formatted representations. Return the key pair as a (public, private) 218 # tuple, where each RSA is a string in PEM format. 219 private_pem = private_key.private_bytes(encoding=serialization.Encoding.PEM, 220 format=serialization.PrivateFormat.TraditionalOpenSSL, 221 encryption_algorithm=serialization.NoEncryption()) 222 223 # Need to generate the public pem from the private key before serialization 224 # to PEM. 225 public_key = private_key.public_key() 226 public_pem = public_key.public_bytes(encoding=serialization.Encoding.PEM, 227 format=serialization.PublicFormat.SubjectPublicKeyInfo) 228 229 return public_pem.decode('utf-8'), private_pem.decode('utf-8') 230 231 232 233 234 235def create_rsa_signature(private_key, data, scheme='rsassa-pss-sha256'): 236 """ 237 <Purpose> 238 Generate a 'scheme' signature. The signature, and the signature scheme 239 used, is returned as a (signature, scheme) tuple. 240 241 The signing process will use 'private_key' to generate the signature of 242 'data'. 243 244 RFC3447 - RSASSA-PSS 245 http://www.ietf.org/rfc/rfc3447.txt 246 247 >>> public, private = generate_rsa_public_and_private(2048) 248 >>> data = 'The quick brown fox jumps over the lazy dog'.encode('utf-8') 249 >>> scheme = 'rsassa-pss-sha256' 250 >>> signature, scheme = create_rsa_signature(private, data, scheme) 251 >>> securesystemslib.formats.NAME_SCHEMA.matches(scheme) 252 True 253 >>> scheme == 'rsassa-pss-sha256' 254 True 255 >>> securesystemslib.formats.PYCACRYPTOSIGNATURE_SCHEMA.matches(signature) 256 True 257 258 <Arguments> 259 private_key: 260 The private RSA key, a string in PEM format. 261 262 data: 263 Data (string) used by create_rsa_signature() to generate the signature. 264 265 scheme: 266 The signature scheme used to generate the signature. 267 268 <Exceptions> 269 securesystemslib.exceptions.FormatError, if 'private_key' is improperly 270 formatted. 271 272 ValueError, if 'private_key' is unset. 273 274 securesystemslib.exceptions.CryptoError, if the signature cannot be 275 generated. 276 277 <Side Effects> 278 pyca/cryptography's 'RSAPrivateKey.signer()' called to generate the 279 signature. 280 281 <Returns> 282 A (signature, scheme) tuple, where the signature is a string and the scheme 283 is one of the supported RSA signature schemes. For example: 284 'rsassa-pss-sha256'. 285 """ 286 287 # Does the arguments have the correct format? 288 # If not, raise 'securesystemslib.exceptions.FormatError' if any of the 289 # checks fail. 290 securesystemslib.formats.PEMRSA_SCHEMA.check_match(private_key) 291 securesystemslib.formats.DATA_SCHEMA.check_match(data) 292 securesystemslib.formats.RSA_SCHEME_SCHEMA.check_match(scheme) 293 294 # Signing 'data' requires a private key. 'rsassa-pss-sha256' is the only 295 # currently supported signature scheme. 296 signature = None 297 298 # Verify the signature, but only if the private key has been set. The 299 # private key is a NULL string if unset. Although it may be clearer to 300 # explicitly check that 'private_key' is not '', we can/should check for a 301 # value and not compare identities with the 'is' keyword. Up to this point 302 # 'private_key' has variable size and can be an empty string. 303 if len(private_key): 304 305 # An if-clause isn't strictly needed here, since 'rsasssa-pss-sha256' is 306 # the only currently supported RSA scheme. Nevertheless, include the 307 # conditional statement to accomodate future schemes that might be added. 308 if scheme == 'rsassa-pss-sha256': 309 # Generate an RSSA-PSS signature. Raise 310 # 'securesystemslib.exceptions.CryptoError' for any of the expected 311 # exceptions raised by pyca/cryptography. 312 try: 313 # 'private_key' (in PEM format) must first be converted to a 314 # pyca/cryptography private key object before a signature can be 315 # generated. 316 private_key_object = load_pem_private_key(private_key.encode('utf-8'), 317 password=None, backend=default_backend()) 318 319 signature = private_key_object.sign( 320 data, padding.PSS(mgf=padding.MGF1(hashes.SHA256()), 321 salt_length=hashes.SHA256().digest_size), hashes.SHA256()) 322 323 # If the PEM data could not be decrypted, or if its structure could not 324 # be decoded successfully. 325 except ValueError: 326 raise securesystemslib.exceptions.CryptoError('The private key' 327 ' (in PEM format) could not be deserialized.') 328 329 # 'TypeError' is raised if a password was given and the private key was 330 # not encrypted, or if the key was encrypted but no password was 331 # supplied. Note: A passphrase or password is not used when generating 332 # 'private_key', since it should not be encrypted. 333 except TypeError: 334 raise securesystemslib.exceptions.CryptoError('The private key was' 335 ' unexpectedly encrypted.') 336 337 # 'cryptography.exceptions.UnsupportedAlgorithm' is raised if the 338 # serialized key is of a type that is not supported by the backend, or if 339 # the key is encrypted with a symmetric cipher that is not supported by 340 # the backend. 341 except cryptography.exceptions.UnsupportedAlgorithm: #pragma: no cover 342 raise securesystemslib.exceptions.CryptoError('The private key is' 343 ' encrypted with an unsupported algorithm.') 344 345 # The RSA_SCHEME_SCHEMA.check_match() above should have validated 'scheme'. 346 # This is a defensive check check.. 347 else: #pragma: no cover 348 raise securesystemslib.exceptions.UnsupportedAlgorithmError('Unsupported' 349 ' signature scheme is specified: ' + repr(scheme)) 350 351 else: 352 raise ValueError('The required private key is unset.') 353 354 return signature, scheme 355 356 357 358 359 360def verify_rsa_signature(signature, signature_scheme, public_key, data): 361 """ 362 <Purpose> 363 Determine whether the corresponding private key of 'public_key' produced 364 'signature'. verify_signature() will use the public key, signature scheme, 365 and 'data' to complete the verification. 366 367 >>> public, private = generate_rsa_public_and_private(2048) 368 >>> data = b'The quick brown fox jumps over the lazy dog' 369 >>> scheme = 'rsassa-pss-sha256' 370 >>> signature, scheme = create_rsa_signature(private, data, scheme) 371 >>> verify_rsa_signature(signature, scheme, public, data) 372 True 373 >>> verify_rsa_signature(signature, scheme, public, b'bad_data') 374 False 375 376 <Arguments> 377 signature: 378 A signature, as a string. This is the signature returned 379 by create_rsa_signature(). 380 381 signature_scheme: 382 A string that indicates the signature scheme used to generate 383 'signature'. 'rsassa-pss-sha256' is currently supported. 384 385 public_key: 386 The RSA public key, a string in PEM format. 387 388 data: 389 Data used by securesystemslib.keys.create_signature() to generate 390 'signature'. 'data' (a string) is needed here to verify 'signature'. 391 392 <Exceptions> 393 securesystemslib.exceptions.FormatError, if 'signature', 394 'signature_scheme', 'public_key', or 'data' are improperly formatted. 395 396 securesystemslib.exceptions.UnsupportedAlgorithmError, if the signature 397 scheme used by 'signature' is not one supported by 398 securesystemslib.keys.create_signature(). 399 400 securesystemslib.exceptions.CryptoError, if the private key cannot be 401 decoded or its key type is unsupported. 402 403 <Side Effects> 404 pyca/cryptography's RSAPublicKey.verifier() called to do the actual 405 verification. 406 407 <Returns> 408 Boolean. True if the signature is valid, False otherwise. 409 """ 410 411 # Does 'public_key' have the correct format? 412 # This check will ensure 'public_key' conforms to 413 # 'securesystemslib.formats.PEMRSA_SCHEMA'. Raise 414 # 'securesystemslib.exceptions.FormatError' if the check fails. 415 securesystemslib.formats.PEMRSA_SCHEMA.check_match(public_key) 416 417 # Does 'signature_scheme' have the correct format? 418 securesystemslib.formats.RSA_SCHEME_SCHEMA.check_match(signature_scheme) 419 420 # Does 'signature' have the correct format? 421 securesystemslib.formats.PYCACRYPTOSIGNATURE_SCHEMA.check_match(signature) 422 423 # What about 'data'? 424 securesystemslib.formats.DATA_SCHEMA.check_match(data) 425 426 # Verify whether the private key of 'public_key' produced 'signature'. 427 # Before returning the 'valid_signature' Boolean result, ensure 'RSASSA-PSS' 428 # was used as the signature scheme. 429 valid_signature = False 430 431 # Verify the RSASSA-PSS signature with pyca/cryptography. 432 try: 433 public_key_object = serialization.load_pem_public_key(public_key.encode('utf-8'), 434 backend=default_backend()) 435 436 # verify() raises 'cryptography.exceptions.InvalidSignature' if the 437 # signature is invalid. 'salt_length' is set to the digest size of the 438 # hashing algorithm. 439 try: 440 public_key_object.verify(signature, data, 441 padding.PSS(mgf=padding.MGF1(hashes.SHA256()), 442 salt_length=hashes.SHA256().digest_size), 443 hashes.SHA256()) 444 return True 445 446 except cryptography.exceptions.InvalidSignature: 447 return False 448 449 # Raised by load_pem_public_key(). 450 except (ValueError, cryptography.exceptions.UnsupportedAlgorithm) as e: 451 raise securesystemslib.exceptions.CryptoError('The PEM could not be' 452 ' decoded successfully, or contained an unsupported key type: ' + str(e)) 453 454 455def create_rsa_encrypted_pem(private_key, passphrase): 456 """ 457 <Purpose> 458 Return a string in PEM format (TraditionalOpenSSL), where the private part 459 of the RSA key is encrypted using the best available encryption for a given 460 key's backend. This is a curated (by cryptography.io) encryption choice and 461 the algorithm may change over time. 462 463 c.f. cryptography.io/en/latest/hazmat/primitives/asymmetric/serialization/ 464 #cryptography.hazmat.primitives.serialization.BestAvailableEncryption 465 466 >>> public, private = generate_rsa_public_and_private(2048) 467 >>> passphrase = 'secret' 468 >>> encrypted_pem = create_rsa_encrypted_pem(private, passphrase) 469 >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(encrypted_pem) 470 True 471 472 <Arguments> 473 private_key: 474 The private key string in PEM format. 475 476 passphrase: 477 The passphrase, or password, to encrypt the private part of the RSA 478 key. 479 480 <Exceptions> 481 securesystemslib.exceptions.FormatError, if the arguments are improperly 482 formatted. 483 484 securesystemslib.exceptions.CryptoError, if the passed RSA key cannot be 485 deserialized by pyca cryptography. 486 487 ValueError, if 'private_key' is unset. 488 489 490 <Returns> 491 A string in PEM format (TraditionalOpenSSL), where the private RSA key is 492 encrypted. Conforms to 'securesystemslib.formats.PEMRSA_SCHEMA'. 493 """ 494 495 # This check will ensure 'private_key' has the appropriate number 496 # of objects and object types, and that all dict keys are properly named. 497 # Raise 'securesystemslib.exceptions.FormatError' if the check fails. 498 securesystemslib.formats.PEMRSA_SCHEMA.check_match(private_key) 499 500 # Does 'passphrase' have the correct format? 501 securesystemslib.formats.PASSWORD_SCHEMA.check_match(passphrase) 502 503 # 'private_key' may still be a NULL string after the 504 # 'securesystemslib.formats.PEMRSA_SCHEMA' so we need an additional check 505 if len(private_key): 506 try: 507 private_key = load_pem_private_key(private_key.encode('utf-8'), 508 password=None, backend=default_backend()) 509 except ValueError: 510 raise securesystemslib.exceptions.CryptoError('The private key' 511 ' (in PEM format) could not be deserialized.') 512 513 else: 514 raise ValueError('The required private key is unset.') 515 516 encrypted_pem = private_key.private_bytes( 517 encoding=serialization.Encoding.PEM, 518 format=serialization.PrivateFormat.TraditionalOpenSSL, 519 encryption_algorithm=serialization.BestAvailableEncryption( 520 passphrase.encode('utf-8'))) 521 522 return encrypted_pem.decode() 523 524 525 526 527 528def create_rsa_public_and_private_from_pem(pem, passphrase=None): 529 """ 530 <Purpose> 531 Generate public and private RSA keys from an optionally encrypted PEM. The 532 public and private keys returned conform to 533 'securesystemslib.formats.PEMRSA_SCHEMA' and have the form: 534 535 '-----BEGIN RSA PUBLIC KEY----- ... -----END RSA PUBLIC KEY-----' 536 537 and 538 539 '-----BEGIN RSA PRIVATE KEY----- ...-----END RSA PRIVATE KEY-----' 540 541 The public and private keys are returned as strings in PEM format. 542 543 In case the private key part of 'pem' is encrypted pyca/cryptography's 544 load_pem_private_key() method is passed passphrase. In the default case 545 here, pyca/cryptography will decrypt with a PBKDF1+MD5 546 strengthened'passphrase', and 3DES with CBC mode for encryption/decryption. 547 Alternatively, key data may be encrypted with AES-CTR-Mode and the 548 passphrase strengthened with PBKDF2+SHA256, although this method is used 549 only with TUF encrypted key files. 550 551 >>> public, private = generate_rsa_public_and_private(2048) 552 >>> passphrase = 'secret' 553 >>> encrypted_pem = create_rsa_encrypted_pem(private, passphrase) 554 >>> returned_public, returned_private = \ 555 create_rsa_public_and_private_from_pem(encrypted_pem, passphrase) 556 >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(returned_public) 557 True 558 >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(returned_private) 559 True 560 >>> public == returned_public 561 True 562 >>> private == returned_private 563 True 564 565 <Arguments> 566 pem: 567 A byte string in PEM format, where the private key can be encrypted. 568 It has the form: 569 570 '-----BEGIN RSA PRIVATE KEY-----\n 571 Proc-Type: 4,ENCRYPTED\nDEK-Info: DES-EDE3-CBC ...' 572 573 passphrase: (optional) 574 The passphrase, or password, to decrypt the private part of the RSA 575 key. 'passphrase' is not directly used as the encryption key, instead 576 it is used to derive a stronger symmetric key. 577 578 <Exceptions> 579 securesystemslib.exceptions.FormatError, if the arguments are improperly 580 formatted. 581 582 securesystemslib.exceptions.CryptoError, if the public and private RSA keys 583 cannot be generated from 'pem', or exported in PEM format. 584 585 <Side Effects> 586 pyca/cryptography's 'serialization.load_pem_private_key()' called to 587 perform the actual conversion from an encrypted RSA private key to 588 PEM format. 589 590 <Returns> 591 A (public, private) tuple containing the RSA keys in PEM format. 592 """ 593 594 # Does 'encryped_pem' have the correct format? 595 # This check will ensure 'pem' has the appropriate number 596 # of objects and object types, and that all dict keys are properly named. 597 # Raise 'securesystemslib.exceptions.FormatError' if the check fails. 598 securesystemslib.formats.PEMRSA_SCHEMA.check_match(pem) 599 600 # If passed, does 'passphrase' have the correct format? 601 if passphrase is not None: 602 securesystemslib.formats.PASSWORD_SCHEMA.check_match(passphrase) 603 passphrase = passphrase.encode('utf-8') 604 605 # Generate a pyca/cryptography key object from 'pem'. The generated 606 # pyca/cryptography key contains the required export methods needed to 607 # generate the PEM-formatted representations of the public and private RSA 608 # key. 609 try: 610 private_key = load_pem_private_key(pem.encode('utf-8'), 611 passphrase, backend=default_backend()) 612 613 # pyca/cryptography's expected exceptions for 'load_pem_private_key()': 614 # ValueError: If the PEM data could not be decrypted. 615 # (possibly because the passphrase is wrong)." 616 # TypeError: If a password was given and the private key was not encrypted. 617 # Or if the key was encrypted but no password was supplied. 618 # UnsupportedAlgorithm: If the private key (or if the key is encrypted with 619 # an unsupported symmetric cipher) is not supported by the backend. 620 except (ValueError, TypeError, cryptography.exceptions.UnsupportedAlgorithm) as e: 621 # Raise 'securesystemslib.exceptions.CryptoError' and pyca/cryptography's 622 # exception message. Avoid propogating pyca/cryptography's exception trace 623 # to avoid revealing sensitive error. 624 raise securesystemslib.exceptions.CryptoError('RSA (public, private) tuple' 625 ' cannot be generated from the encrypted PEM string: ' + str(e)) 626 627 # Export the public and private halves of the pyca/cryptography RSA key 628 # object. The (public, private) tuple returned contains the public and 629 # private RSA keys in PEM format, as strings. 630 # Extract the public & private halves of the RSA key and generate their 631 # PEM-formatted representations. Return the key pair as a (public, private) 632 # tuple, where each RSA is a string in PEM format. 633 private_pem = private_key.private_bytes(encoding=serialization.Encoding.PEM, 634 format=serialization.PrivateFormat.TraditionalOpenSSL, 635 encryption_algorithm=serialization.NoEncryption()) 636 637 # Need to generate the public key from the private one before serializing 638 # to PEM format. 639 public_key = private_key.public_key() 640 public_pem = public_key.public_bytes(encoding=serialization.Encoding.PEM, 641 format=serialization.PublicFormat.SubjectPublicKeyInfo) 642 643 return public_pem.decode(), private_pem.decode() 644 645 646 647 648 649def encrypt_key(key_object, password): 650 """ 651 <Purpose> 652 Return a string containing 'key_object' in encrypted form. Encrypted 653 strings may be safely saved to a file. The corresponding decrypt_key() 654 function can be applied to the encrypted string to restore the original key 655 object. 'key_object' is a TUF key (e.g., RSAKEY_SCHEMA, 656 ED25519KEY_SCHEMA). This function calls the pyca/cryptography library to 657 perform the encryption and derive a suitable encryption key. 658 659 Whereas an encrypted PEM file uses the Triple Data Encryption Algorithm 660 (3DES), the Cipher-block chaining (CBC) mode of operation, and the Password 661 Based Key Derivation Function 1 (PBKF1) + MD5 to strengthen 'password', 662 encrypted TUF keys use AES-256-CTR-Mode and passwords strengthened with 663 PBKDF2-HMAC-SHA256 (100K iterations by default, but may be overriden in 664 'settings.PBKDF2_ITERATIONS' by the user). 665 666 http://en.wikipedia.org/wiki/Advanced_Encryption_Standard 667 http://en.wikipedia.org/wiki/CTR_mode#Counter_.28CTR.29 668 https://en.wikipedia.org/wiki/PBKDF2 669 670 >>> ed25519_key = {'keytype': 'ed25519', \ 671 'scheme': 'ed25519', \ 672 'keyid': \ 673 'd62247f817883f593cf6c66a5a55292488d457bcf638ae03207dbbba9dbe457d', \ 674 'keyval': {'public': \ 675 '74addb5ad544a4306b34741bc1175a3613a8d7dc69ff64724243efdec0e301ad', \ 676 'private': \ 677 '1f26964cc8d4f7ee5f3c5da2fbb7ab35811169573ac367b860a537e47789f8c4'}} 678 >>> passphrase = 'secret' 679 >>> encrypted_key = encrypt_key(ed25519_key, passphrase) 680 >>> securesystemslib.formats.ENCRYPTEDKEY_SCHEMA.matches(encrypted_key.encode('utf-8')) 681 True 682 683 <Arguments> 684 key_object: 685 The TUF key object that should contain the private portion of the ED25519 686 key. 687 688 password: 689 The password, or passphrase, to encrypt the private part of the RSA 690 key. 'password' is not used directly as the encryption key, a stronger 691 encryption key is derived from it. 692 693 <Exceptions> 694 securesystemslib.exceptions.FormatError, if any of the arguments are 695 improperly formatted or 'key_object' does not contain the private portion 696 of the key. 697 698 securesystemslib.exceptions.CryptoError, if an Ed25519 key in encrypted TUF 699 format cannot be created. 700 701 <Side Effects> 702 pyca/Cryptography cryptographic operations called to perform the actual 703 encryption of 'key_object'. 'password' used to derive a suitable 704 encryption key. 705 706 <Returns> 707 An encrypted string in 'securesystemslib.formats.ENCRYPTEDKEY_SCHEMA' format. 708 """ 709 710 # Do the arguments have the correct format? 711 # Ensure the arguments have the appropriate number of objects and object 712 # types, and that all dict keys are properly named. 713 # Raise 'securesystemslib.exceptions.FormatError' if the check fails. 714 securesystemslib.formats.ANYKEY_SCHEMA.check_match(key_object) 715 716 # Does 'password' have the correct format? 717 securesystemslib.formats.PASSWORD_SCHEMA.check_match(password) 718 719 # Ensure the private portion of the key is included in 'key_object'. 720 if 'private' not in key_object['keyval'] or not key_object['keyval']['private']: 721 raise securesystemslib.exceptions.FormatError('Key object does not contain' 722 ' a private part.') 723 724 # Derive a key (i.e., an appropriate encryption key and not the 725 # user's password) from the given 'password'. Strengthen 'password' with 726 # PBKDF2-HMAC-SHA256 (100K iterations by default, but may be overriden in 727 # 'settings.PBKDF2_ITERATIONS' by the user). 728 salt, iterations, derived_key = _generate_derived_key(password) 729 730 # Store the derived key info in a dictionary, the object expected 731 # by the non-public _encrypt() routine. 732 derived_key_information = {'salt': salt, 'iterations': iterations, 733 'derived_key': derived_key} 734 735 # Convert the key object to json string format and encrypt it with the 736 # derived key. 737 encrypted_key = _encrypt(json.dumps(key_object), derived_key_information) 738 739 return encrypted_key 740 741 742 743 744 745def decrypt_key(encrypted_key, password): 746 """ 747 <Purpose> 748 Return a string containing 'encrypted_key' in non-encrypted form. 749 The decrypt_key() function can be applied to the encrypted string to restore 750 the original key object, a TUF key (e.g., RSAKEY_SCHEMA, ED25519KEY_SCHEMA). 751 This function calls the appropriate cryptography module (i.e., 752 pyca_crypto_keys.py) to perform the decryption. 753 754 Encrypted TUF keys use AES-256-CTR-Mode and passwords strengthened with 755 PBKDF2-HMAC-SHA256 (100K iterations be default, but may be overriden in 756 'settings.py' by the user). 757 758 http://en.wikipedia.org/wiki/Advanced_Encryption_Standard 759 http://en.wikipedia.org/wiki/CTR_mode#Counter_.28CTR.29 760 https://en.wikipedia.org/wiki/PBKDF2 761 762 >>> ed25519_key = {'keytype': 'ed25519', \ 763 'scheme': 'ed25519', \ 764 'keyid': \ 765 'd62247f817883f593cf6c66a5a55292488d457bcf638ae03207dbbba9dbe457d', \ 766 'keyval': {'public': \ 767 '74addb5ad544a4306b34741bc1175a3613a8d7dc69ff64724243efdec0e301ad', \ 768 'private': \ 769 '1f26964cc8d4f7ee5f3c5da2fbb7ab35811169573ac367b860a537e47789f8c4'}} 770 >>> passphrase = 'secret' 771 >>> encrypted_key = encrypt_key(ed25519_key, passphrase) 772 >>> decrypted_key = decrypt_key(encrypted_key.encode('utf-8'), passphrase) 773 >>> securesystemslib.formats.ED25519KEY_SCHEMA.matches(decrypted_key) 774 True 775 >>> decrypted_key == ed25519_key 776 True 777 778 <Arguments> 779 encrypted_key: 780 An encrypted TUF key (additional data is also included, such as salt, 781 number of password iterations used for the derived encryption key, etc) 782 of the form 'securesystemslib.formats.ENCRYPTEDKEY_SCHEMA'. 783 'encrypted_key' should have been generated with encrypted_key(). 784 785 password: 786 The password, or passphrase, to encrypt the private part of the RSA 787 key. 'password' is not used directly as the encryption key, a stronger 788 encryption key is derived from it. 789 790 <Exceptions> 791 securesystemslib.exceptions.FormatError, if the arguments are improperly 792 formatted. 793 794 securesystemslib.exceptions.CryptoError, if a TUF key cannot be decrypted 795 from 'encrypted_key'. 796 797 securesystemslib.exceptions.Error, if a valid TUF key object is not found in 798 'encrypted_key'. 799 800 <Side Effects> 801 The pyca/cryptography is library called to perform the actual decryption 802 of 'encrypted_key'. The key derivation data stored in 'encrypted_key' is 803 used to re-derive the encryption/decryption key. 804 805 <Returns> 806 The decrypted key object in 'securesystemslib.formats.ANYKEY_SCHEMA' format. 807 """ 808 809 # Do the arguments have the correct format? 810 # Ensure the arguments have the appropriate number of objects and object 811 # types, and that all dict keys are properly named. 812 # Raise 'securesystemslib.exceptions.FormatError' if the check fails. 813 securesystemslib.formats.ENCRYPTEDKEY_SCHEMA.check_match(encrypted_key) 814 815 # Does 'password' have the correct format? 816 securesystemslib.formats.PASSWORD_SCHEMA.check_match(password) 817 818 # Decrypt 'encrypted_key', using 'password' (and additional key derivation 819 # data like salts and password iterations) to re-derive the decryption key. 820 json_data = _decrypt(encrypted_key, password) 821 822 # Raise 'securesystemslib.exceptions.Error' if 'json_data' cannot be 823 # deserialized to a valid 'securesystemslib.formats.ANYKEY_SCHEMA' key 824 # object. 825 key_object = securesystemslib.util.load_json_string(json_data.decode()) 826 827 return key_object 828 829 830 831 832 833def _generate_derived_key(password, salt=None, iterations=None): 834 """ 835 Generate a derived key by feeding 'password' to the Password-Based Key 836 Derivation Function (PBKDF2). pyca/cryptography's PBKDF2 implementation is 837 used in this module. 'salt' may be specified so that a previous derived key 838 may be regenerated, otherwise '_SALT_SIZE' is used by default. 'iterations' 839 is the number of SHA-256 iterations to perform, otherwise 840 '_PBKDF2_ITERATIONS' is used by default. 841 """ 842 843 # Use pyca/cryptography's default backend (e.g., openSSL, CommonCrypto, etc.) 844 # The default backend is not fixed and can be changed by pyca/cryptography 845 # over time. 846 backend = default_backend() 847 848 # If 'salt' and 'iterations' are unspecified, a new derived key is generated. 849 # If specified, a deterministic key is derived according to the given 850 # 'salt' and 'iterrations' values. 851 if salt is None: 852 salt = os.urandom(_SALT_SIZE) 853 854 if iterations is None: 855 iterations = _PBKDF2_ITERATIONS 856 857 # Derive an AES key with PBKDF2. The 'length' is the desired key length of 858 # the derived key. 859 pbkdf_object = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt, 860 iterations=iterations, backend=backend) 861 862 derived_key = pbkdf_object.derive(password.encode('utf-8')) 863 864 return salt, iterations, derived_key 865 866 867 868 869 870def _encrypt(key_data, derived_key_information): 871 """ 872 Encrypt 'key_data' using the Advanced Encryption Standard (AES-256) algorithm. 873 'derived_key_information' should contain a key strengthened by PBKDF2. The 874 key size is 256 bits and AES's mode of operation is set to CTR (CounTeR Mode). 875 The HMAC of the ciphertext is generated to ensure the ciphertext has not been 876 modified. 877 878 'key_data' is the JSON string representation of the key. In the case 879 of RSA keys, this format would be 'securesystemslib.formats.RSAKEY_SCHEMA': 880 881 {'keytype': 'rsa', 882 'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...', 883 'private': '-----BEGIN RSA PRIVATE KEY----- ...'}} 884 885 'derived_key_information' is a dictionary of the form: 886 {'salt': '...', 887 'derived_key': '...', 888 'iterations': '...'} 889 890 'securesystemslib.exceptions.CryptoError' raised if the encryption fails. 891 """ 892 893 # Generate a random Initialization Vector (IV). Follow the provably secure 894 # encrypt-then-MAC approach, which affords the ability to verify ciphertext 895 # without needing to decrypt it and preventing an attacker from feeding the 896 # block cipher malicious data. Modes like GCM provide both encryption and 897 # authentication, whereas CTR only provides encryption. 898 899 # Generate a random 128-bit IV. Random bits of data is needed for salts and 900 # initialization vectors suitable for the encryption algorithms used in 901 # 'pyca_crypto_keys.py'. 902 iv = os.urandom(16) 903 904 # Construct an AES-CTR Cipher object with the given key and a randomly 905 # generated IV. 906 symmetric_key = derived_key_information['derived_key'] 907 encryptor = Cipher(algorithms.AES(symmetric_key), modes.CTR(iv), 908 backend=default_backend()).encryptor() 909 910 # Encrypt the plaintext and get the associated ciphertext. 911 # Do we need to check for any exceptions? 912 ciphertext = encryptor.update(key_data.encode('utf-8')) + encryptor.finalize() 913 914 # Generate the hmac of the ciphertext to ensure it has not been modified. 915 # The decryption routine may verify a ciphertext without having to perform 916 # a decryption operation. 917 symmetric_key = derived_key_information['derived_key'] 918 salt = derived_key_information['salt'] 919 hmac_object = \ 920 cryptography.hazmat.primitives.hmac.HMAC(symmetric_key, hashes.SHA256(), 921 backend=default_backend()) 922 hmac_object.update(ciphertext) 923 hmac_value = binascii.hexlify(hmac_object.finalize()) 924 925 # Store the number of PBKDF2 iterations used to derive the symmetric key so 926 # that the decryption routine can regenerate the symmetric key successfully. 927 # The PBKDF2 iterations are allowed to vary for the keys loaded and saved. 928 iterations = derived_key_information['iterations'] 929 930 # Return the salt, iterations, hmac, initialization vector, and ciphertext 931 # as a single string. These five values are delimited by 932 # '_ENCRYPTION_DELIMITER' to make extraction easier. This delimiter is 933 # arbitrarily chosen and should not occur in the hexadecimal representations 934 # of the fields it is separating. 935 return binascii.hexlify(salt).decode() + _ENCRYPTION_DELIMITER + \ 936 str(iterations) + _ENCRYPTION_DELIMITER + \ 937 hmac_value.decode() + _ENCRYPTION_DELIMITER + \ 938 binascii.hexlify(iv).decode() + _ENCRYPTION_DELIMITER + \ 939 binascii.hexlify(ciphertext).decode() 940 941 942 943 944 945def _decrypt(file_contents, password): 946 """ 947 The corresponding decryption routine for _encrypt(). 948 949 'securesystemslib.exceptions.CryptoError' raised if the decryption fails. 950 """ 951 952 # Extract the salt, iterations, hmac, initialization vector, and ciphertext 953 # from 'file_contents'. These five values are delimited by 954 # '_ENCRYPTION_DELIMITER'. This delimiter is arbitrarily chosen and should 955 # not occur in the hexadecimal representations of the fields it is 956 # separating. Raise 'securesystemslib.exceptions.CryptoError', if 957 # 'file_contents' does not contains the expected data layout. 958 try: 959 salt, iterations, hmac, iv, ciphertext = \ 960 file_contents.split(_ENCRYPTION_DELIMITER) 961 962 except ValueError: 963 raise securesystemslib.exceptions.CryptoError('Invalid encrypted file.') 964 965 # Ensure we have the expected raw data for the delimited cryptographic data. 966 salt = binascii.unhexlify(salt.encode('utf-8')) 967 iterations = int(iterations) 968 iv = binascii.unhexlify(iv.encode('utf-8')) 969 ciphertext = binascii.unhexlify(ciphertext.encode('utf-8')) 970 971 # Generate derived key from 'password'. The salt and iterations are 972 # specified so that the expected derived key is regenerated correctly. 973 # Discard the old "salt" and "iterations" values, as we only need the old 974 # derived key. 975 junk_old_salt, junk_old_iterations, symmetric_key = \ 976 _generate_derived_key(password, salt, iterations) 977 978 # Verify the hmac to ensure the ciphertext is valid and has not been altered. 979 # See the encryption routine for why we use the encrypt-then-MAC approach. 980 # The decryption routine may verify a ciphertext without having to perform 981 # a decryption operation. 982 generated_hmac_object = \ 983 cryptography.hazmat.primitives.hmac.HMAC(symmetric_key, hashes.SHA256(), 984 backend=default_backend()) 985 generated_hmac_object.update(ciphertext) 986 generated_hmac = binascii.hexlify(generated_hmac_object.finalize()) 987 988 989 if not securesystemslib.util.digests_are_equal(generated_hmac.decode(), hmac): 990 raise securesystemslib.exceptions.CryptoError('Decryption failed.') 991 992 # Construct a Cipher object, with the key and iv. 993 decryptor = Cipher(algorithms.AES(symmetric_key), modes.CTR(iv), 994 backend=default_backend()).decryptor() 995 996 # Decryption gets us the authenticated plaintext. 997 plaintext = decryptor.update(ciphertext) + decryptor.finalize() 998 999 return plaintext 1000 1001 1002 1003 1004 1005if __name__ == '__main__': 1006 # The interactive sessions of the documentation strings can be tested by 1007 # running 'pyca_crypto_keys.py' as a standalone module: 1008 # $ python pyca_crypto_keys.py 1009 import doctest 1010 doctest.testmod() 1011