1<?php 2namespace Aws\Crypto; 3 4use GuzzleHttp\Psr7; 5use GuzzleHttp\Psr7\AppendStream; 6use GuzzleHttp\Psr7\Stream; 7 8trait EncryptionTrait 9{ 10 private static $allowedOptions = [ 11 'Cipher' => true, 12 'KeySize' => true, 13 'Aad' => true, 14 ]; 15 16 /** 17 * Dependency to generate a CipherMethod from a set of inputs for loading 18 * in to an AesEncryptingStream. 19 * 20 * @param string $cipherName Name of the cipher to generate for encrypting. 21 * @param string $iv Base Initialization Vector for the cipher. 22 * @param int $keySize Size of the encryption key, in bits, that will be 23 * used. 24 * 25 * @return Cipher\CipherMethod 26 * 27 * @internal 28 */ 29 abstract protected function buildCipherMethod($cipherName, $iv, $keySize); 30 31 /** 32 * Builds an AesStreamInterface and populates encryption metadata into the 33 * supplied envelope. 34 * 35 * @param Stream $plaintext Plain-text data to be encrypted using the 36 * materials, algorithm, and data provided. 37 * @param array $cipherOptions Options for use in determining the cipher to 38 * be used for encrypting data. 39 * @param MaterialsProvider $provider A provider to supply and encrypt 40 * materials used in encryption. 41 * @param MetadataEnvelope $envelope A storage envelope for encryption 42 * metadata to be added to. 43 * 44 * @return AesStreamInterface 45 * 46 * @throws \InvalidArgumentException Thrown when a value in $cipherOptions 47 * is not valid. 48 * 49 * @internal 50 */ 51 public function encrypt( 52 Stream $plaintext, 53 array $cipherOptions, 54 MaterialsProvider $provider, 55 MetadataEnvelope $envelope 56 ) { 57 $materialsDescription = $provider->getMaterialsDescription(); 58 59 $cipherOptions = array_intersect_key( 60 $cipherOptions, 61 self::$allowedOptions 62 ); 63 64 if (empty($cipherOptions['Cipher'])) { 65 throw new \InvalidArgumentException('An encryption cipher must be' 66 . ' specified in the "cipher_options".'); 67 } 68 69 if (!self::isSupportedCipher($cipherOptions['Cipher'])) { 70 throw new \InvalidArgumentException('The cipher requested is not' 71 . ' supported by the SDK.'); 72 } 73 74 if (empty($cipherOptions['KeySize'])) { 75 $cipherOptions['KeySize'] = 256; 76 } 77 if (!is_int($cipherOptions['KeySize'])) { 78 throw new \InvalidArgumentException('The cipher "KeySize" must be' 79 . ' an integer.'); 80 } 81 82 if (!MaterialsProvider::isSupportedKeySize( 83 $cipherOptions['KeySize'] 84 )) { 85 throw new \InvalidArgumentException('The cipher "KeySize" requested' 86 . ' is not supported by AES (128, 192, or 256).'); 87 } 88 89 $cipherOptions['Iv'] = $provider->generateIv( 90 $this->getCipherOpenSslName( 91 $cipherOptions['Cipher'], 92 $cipherOptions['KeySize'] 93 ) 94 ); 95 96 $cek = $provider->generateCek($cipherOptions['KeySize']); 97 98 list($encryptingStream, $aesName) = $this->getEncryptingStream( 99 $plaintext, 100 $cek, 101 $cipherOptions 102 ); 103 104 // Populate envelope data 105 $envelope[MetadataEnvelope::CONTENT_KEY_V2_HEADER] = 106 $provider->encryptCek( 107 $cek, 108 $materialsDescription 109 ); 110 unset($cek); 111 112 $envelope[MetadataEnvelope::IV_HEADER] = 113 base64_encode($cipherOptions['Iv']); 114 $envelope[MetadataEnvelope::KEY_WRAP_ALGORITHM_HEADER] = 115 $provider->getWrapAlgorithmName(); 116 $envelope[MetadataEnvelope::CONTENT_CRYPTO_SCHEME_HEADER] = $aesName; 117 $envelope[MetadataEnvelope::UNENCRYPTED_CONTENT_LENGTH_HEADER] = 118 strlen($plaintext); 119 $envelope[MetadataEnvelope::MATERIALS_DESCRIPTION_HEADER] = 120 json_encode($materialsDescription); 121 if (!empty($cipherOptions['Tag'])) { 122 $envelope[MetadataEnvelope::CRYPTO_TAG_LENGTH_HEADER] = 123 strlen($cipherOptions['Tag']) * 8; 124 } 125 126 return $encryptingStream; 127 } 128 129 /** 130 * Generates a stream that wraps the plaintext with the proper cipher and 131 * uses the content encryption key (CEK) to encrypt the data when read. 132 * 133 * @param Stream $plaintext Plain-text data to be encrypted using the 134 * materials, algorithm, and data provided. 135 * @param string $cek A content encryption key for use by the stream for 136 * encrypting the plaintext data. 137 * @param array $cipherOptions Options for use in determining the cipher to 138 * be used for encrypting data. 139 * 140 * @return [AesStreamInterface, string] 141 * 142 * @internal 143 */ 144 protected function getEncryptingStream( 145 Stream $plaintext, 146 $cek, 147 &$cipherOptions 148 ) { 149 switch ($cipherOptions['Cipher']) { 150 case 'gcm': 151 $cipherOptions['TagLength'] = 16; 152 153 $cipherTextStream = new AesGcmEncryptingStream( 154 $plaintext, 155 $cek, 156 $cipherOptions['Iv'], 157 $cipherOptions['Aad'] = isset($cipherOptions['Aad']) 158 ? $cipherOptions['Aad'] 159 : null, 160 $cipherOptions['TagLength'], 161 $cipherOptions['KeySize'] 162 ); 163 164 if (!empty($cipherOptions['Aad'])) { 165 trigger_error("'Aad' has been supplied for content encryption" 166 . " with " . $cipherTextStream->getAesName() . ". The" 167 . " PHP SDK encryption client can decrypt an object" 168 . " encrypted in this way, but other AWS SDKs may not be" 169 . " able to.", E_USER_WARNING); 170 } 171 172 $appendStream = new AppendStream([ 173 $cipherTextStream->createStream() 174 ]); 175 $cipherOptions['Tag'] = $cipherTextStream->getTag(); 176 $appendStream->addStream(Psr7\Utils::streamFor($cipherOptions['Tag'])); 177 return [$appendStream, $cipherTextStream->getAesName()]; 178 default: 179 $cipherMethod = $this->buildCipherMethod( 180 $cipherOptions['Cipher'], 181 $cipherOptions['Iv'], 182 $cipherOptions['KeySize'] 183 ); 184 $cipherTextStream = new AesEncryptingStream( 185 $plaintext, 186 $cek, 187 $cipherMethod 188 ); 189 return [$cipherTextStream, $cipherTextStream->getAesName()]; 190 } 191 } 192} 193