/** * Creates a symmetric key. * * The supported formats are: * * - `php` - JSON web key formatted as a PHP associative array * - `json` - JSON web key * - `jwe` - Encrypted JSON web key * - `base64url` - the symmetric key encoded in Base64url format * - `base64` - the symmetric key encoded in Base64 format * - `bin` - the symmetric key encoded in binary format * * @param string|array $data the key data * @param string $format the format * @param string $password the password, if the key is password protected * @param string $alg the algorithm, if the key is password protected */ public function __construct($data, $format, $password = null, $alg = 'PBES2-HS256+A128KW') { switch ($format) { case 'php': case 'json': case 'jwe': parent::__construct($data, $format, $password, $alg); break; case 'base64url': $jwk = array('kty' => self::KTY, 'k' => $data); parent::__construct($jwk); break; case 'base64': $jwk = array('kty' => self::KTY, 'k' => Util::base64url_encode(base64_decode($data))); parent::__construct($jwk); break; case 'bin': $jwk = array('kty' => self::KTY, 'k' => Util::base64url_encode($data)); parent::__construct($jwk); break; default: throw new KeyException('Incorrect format'); } if (!isset($this->data['kty'])) { $this->data['kty'] = self::KTY; } }
public function sign($data, $keys, $kid = null) { $key = $this->getSigningKey($keys, $kid); if ($key == null || !is_a($key, 'SimpleJWT\\Keys\\SymmetricKey')) { throw new KeyException('Key not found or is invalid'); } return Util::base64url_encode(hash_hmac('sha' . $this->size, $data, $key->toBinary(), true)); }
function testPBES2Decrypt() { $encrypted_key = 'TrqXOwuNUfDV9VPTNbyGvEJ9JMjefAVn-TR1uIxR9p6hsRQh9Tk7BA'; $password = '******'; $keys = $this->getKeySet($password); $headers = array('alg' => 'PBES2-HS256+A128KW', 'p2c' => 4096, 'p2s' => '2WCTcJZ1Rvd_CJuJripQ1w'); $alg = new PBES2('PBES2-HS256+A128KW'); $cek = $alg->decryptKey($encrypted_key, $keys, $headers); $this->assertEquals('bxsZNEIdFE5csDjwQdBScKGDJDfK7LmsgReZwsMw_bY', Util::base64url_encode($cek)); }
/** * Signs and serialises the JWT. * * @param SimpleJWT\Keys\KeySet $keys the key set containing the key to sign the * JWT * @param string $kid the ID of the key to use to sign. If null, this * is automatically retrieved * @param bool|array $auto_complete an array of headers or claims that * should be automatically completed, or false if no auto-completion is * to be performed * @param string $alg if not null, override the `alg` header * @param string $format the JWT serialisation format * @return string the signed and serialised JWT * @throws SimpleJWT\Keys\KeyException if there is an error obtaining the key * to sign the JWT * @throws SimpleJWT\Crypt\CryptException if there is a cryptographic error */ public function encode($keys, $kid = null, $auto_complete = array('iat', 'kid'), $alg = null, $format = self::COMPACT_FORMAT) { if ($auto_complete === false) { $auto_complete = array(); } if ($alg != null) { $this->headers['alg'] = $alg; } if (in_array('iat', $auto_complete) && !isset($this->claims['iat'])) { $this->claims['iat'] = time(); } try { $signer = AlgorithmFactory::create($this->headers['alg']); } catch (\UnexpectedValueException $e) { throw new CryptException($e->getMessage(), 0, $e); } $key = $signer->getSigningKey($keys, $kid); if ($key != null && in_array('kid', $auto_complete)) { $this->headers['kid'] = $key->getKeyId(); } $protected = Util::base64url_encode(json_encode($this->headers)); $payload = Util::base64url_encode(json_encode($this->claims)); $signing_input = $protected . '.' . $payload; $signature = $signer->sign($signing_input, $keys, $kid); switch ($format) { case self::COMPACT_FORMAT: return $signing_input . '.' . $signature; case self::JSON_FORMAT: return json_encode(array('protected' => $protected, 'payload' => $payload, 'signature' => $signature)); default: throw new \InvalidArgumentException('Incorrect format'); } }
public function encryptAndSign($plaintext, $cek, $additional, $iv = null) { $params = self::$alg_params[$this->getAlg()]; if (strlen($cek) != $this->getCEKSize() / 8) { throw new CryptException('Incorrect key length'); } if ($iv == null) { $iv = openssl_random_pseudo_bytes($this->getIVSize() / 8); } else { $iv = Util::base64url_decode($iv); if (strlen($iv) != $this->getIVSize() / 8) { throw new CryptException('Incorrect IV length'); } } list($mac_key, $enc_key) = str_split($cek, (int) (strlen($cek) / 2)); $al = Util::packInt64(strlen($additional) * 8); $e = openssl_encrypt($plaintext, $params['cipher'], $enc_key, OPENSSL_RAW_DATA, $iv); $m = hash_hmac($params['hash'], $additional . $iv . $e . $al, $mac_key, true); $t = substr($m, 0, $params['tag']); return array('ciphertext' => Util::base64url_encode($e), 'tag' => Util::base64url_encode($t), 'iv' => Util::base64url_encode($iv)); }
protected function base64url($base64) { return Util::base64url_encode(base64_decode($base64)); }
/** * Creates an RSA key. * * The supported formats are: * * - `php` - JSON web key formatted as a PHP associative array * - `json` - JSON web key * - `jwe` - Encrypted JSON web key * - `pem` - the public or private key encoded in PEM (base64 encoded DER) format * * @param string|array $data the key data * @param string $format the format * @param string $password the password, if the key is password protected * @param string $alg the algorithm, if the key is password protected */ public function __construct($data, $format, $password = null, $alg = 'PBES2-HS256+A128KW') { switch ($format) { case 'php': case 'json': case 'jwe': parent::__construct($data, $format, $password, $alg); break; case 'pem': $offset = 0; $jwk = array(); if (preg_match(Key::PEM_PUBLIC, $data, $matches)) { $der = base64_decode($matches[1]); if ($der === FALSE) { throw new KeyException('Cannot read PEM key'); } $offset += ASN1::readDER($der, $offset, $value); // SEQUENCE $offset += ASN1::readDER($der, $offset, $value); // SEQUENCE $offset += ASN1::readDER($der, $offset, $algorithm); // OBJECT IDENTIFIER - AlgorithmIdentifier $algorithm = ASN1::decodeOID($algorithm); if ($algorithm != self::OID) { throw new KeyException('Not RSA key'); } $offset += ASN1::readDER($der, $offset, $value); // NULL - parameters $offset += ASN1::readDER($der, $offset, $value, true); // BIT STRING $offset += ASN1::readDER($der, $offset, $value); // SEQUENCE $offset += ASN1::readDER($der, $offset, $n); // INTEGER [n] $offset += ASN1::readDER($der, $offset, $e); // INTEGER [e] $jwk['kty'] = self::KTY; $jwk['n'] = Util::base64url_encode($n); $jwk['e'] = Util::base64url_encode($e); } elseif (preg_match(self::PEM_PRIVATE, $data, $matches)) { $der = base64_decode($matches[1]); if ($der === FALSE) { throw new KeyException('Cannot read PEM key'); } $offset += ASN1::readDER($der, $offset, $data); // SEQUENCE $offset += ASN1::readDER($der, $offset, $version); // INTEGER if (ord($version) != 0) { throw new KeyException('Unsupported RSA private key version'); } $offset += ASN1::readDER($der, $offset, $n); // INTEGER [n] $offset += ASN1::readDER($der, $offset, $e); // INTEGER [e] $offset += ASN1::readDER($der, $offset, $d); // INTEGER [d] $offset += ASN1::readDER($der, $offset, $p); // INTEGER [p] $offset += ASN1::readDER($der, $offset, $q); // INTEGER [q] $offset += ASN1::readDER($der, $offset, $dp); // INTEGER [dp] $offset += ASN1::readDER($der, $offset, $dq); // INTEGER [dq] $offset += ASN1::readDER($der, $offset, $qi); // INTEGER [qi] if (strlen($der) > $offset) { ASN1::readDER($der, $offset, $oth); } // INTEGER [other] $jwk['kty'] = self::KTY; $jwk['n'] = Util::base64url_encode($n); $jwk['e'] = Util::base64url_encode($e); $jwk['d'] = Util::base64url_encode($d); $jwk['p'] = Util::base64url_encode($p); $jwk['q'] = Util::base64url_encode($q); $jwk['dp'] = Util::base64url_encode($dp); $jwk['dq'] = Util::base64url_encode($dq); $jwk['qi'] = Util::base64url_encode($qi); } parent::__construct($jwk); break; default: throw new KeyException('Incorrect format'); } if (!isset($this->data['kty'])) { $this->data['kty'] = self::KTY; } }
/** * Encrypts the JWE. * * @param SimpleJWT\Keys\KeySet $keys the key set containing the key to encrypt the * content encryption key * @param string $kid the ID of the key to use to encrypt. If null, this * is automatically retrieved * @param string $format the JWE serialisation format * @return string the encrypted JWE * @throws SimpleJWT\Keys\KeyException if there is an error obtaining the key * to sign the JWT * @throws SimpleJWT\Crypt\CryptException if there is a cryptographic error */ public function encrypt($keys, $kid = null, $format = self::COMPACT_FORMAT) { if (!isset($this->headers['alg'])) { throw new \InvalidArgumentException('alg parameter missing'); } if (!isset($this->headers['enc'])) { throw new \InvalidArgumentException('enc parameter missing'); } $key_enc = AlgorithmFactory::create($this->headers['alg']); $content_enc = AlgorithmFactory::create($this->headers['enc']); if ($kid != null) { $this->headers['kid'] = $kid; } if ($key_enc instanceof KeyDerivationAlgorithm) { $agreed_key = $key_enc->deriveKey($keys, $this->headers, $kid); if ($key_enc instanceof KeyEncryptionAlgorithm) { // Key agreement with wrapping $keys->add(new SymmetricKey(array('kty' => SymmetricKey::KTY, 'alg' => $this->headers['alg'], 'k' => Util::base64url_encode($agreed_key)), 'php')); } else { // Direct key agreement or direct encryption $cek = $agreed_key; } } if (!isset($cek)) { $cek = Util::random_bytes($content_enc->getCEKSize() / 8); } if ($key_enc instanceof KeyEncryptionAlgorithm) { $encrypted_key = $key_enc->encryptKey($cek, $keys, $this->headers, $kid); } else { $encrypted_key = ''; } if (isset($this->headers['zip'])) { switch ($this->headers['zip']) { case 'DEF': $plaintext = gzdeflate($this->plaintext); break; default: throw new \InvalidArgumentException('Unsupported zip header:' . $this->headers['zip']); } } else { $plaintext = $this->plaintext; } $protected = Util::base64url_encode(json_encode($this->headers)); $results = $content_enc->encryptAndSign($plaintext, $cek, $protected); $ciphertext = $results['ciphertext']; $iv = isset($results['iv']) ? $results['iv'] : ''; $tag = $results['tag']; switch ($format) { case self::COMPACT_FORMAT: return $protected . '.' . $encrypted_key . '.' . $iv . '.' . $ciphertext . '.' . $tag; case self::JSON_FORMAT: $obj = array('protected' => $protected, 'ciphertext' => $ciphertext, 'tag' => $tag, 'encrypted_key' => $encrypted_key); if ($iv) { $obj['iv'] = $iv; } return json_encode($obj); default: throw new \InvalidArgumentException('Incorrect format'); } }
public function sign($data, $keys, $kid = null) { $key = $this->getSigningKey($keys, $kid); if ($key == null) { throw new KeyException('Key not found or is invalid'); } $binary = ''; if (!openssl_sign($data, $binary, $key->toPEM(), 'SHA' . $this->size)) { $messages = array(); while ($message = openssl_error_string()) { $messages[] = $message; } throw new CryptException('Cannot calculate signature: ' . implode("\n", $messages)); } if ($key->getKeyType() == \SimpleJWT\Keys\ECKey::KTY) { // OpenSSL returns ECDSA signatures as an ASN.1 DER SEQUENCE $offset = 0; $offset += ASN1::readDER($binary, $offset, $value); // SEQUENCE $offset += ASN1::readDER($binary, $offset, $r); // INTEGER $offset += ASN1::readDER($binary, $offset, $s); // INTEGER // DER integers are big-endian in two's complement form. We need to // convert these to unsigned integers. But the r and s values are always // positive, so it is just a matter of stripping out the leading null $r = ltrim($r, ""); $s = ltrim($s, ""); // Now pad out r and s so that they are $key->getSize() bits long $r = str_pad($r, $key->getSize() / 8, "", STR_PAD_LEFT); $s = str_pad($s, $key->getSize() / 8, "", STR_PAD_LEFT); $binary = $r . $s; } return Util::base64url_encode($binary); }
public function encryptKey($cek, $keys, &$headers, $kid = null) { $salt_input = $this->generateSaltInput(); $headers['p2s'] = Util::base64url_encode($salt_input); $headers['p2c'] = $this->iterations; $key = $this->selectKey($keys, $kid); if ($key == null) { throw new CryptException('Key not found or is invalid'); } $derived_keyset = $this->getKeySetFromPassword($key->toBinary(), $headers); return $this->aeskw->encryptKey($cek, $derived_keyset, $headers); }
/** * Obtains a signature for the key. The signature is derived from the * keys to the JSON web key object as returned by the {@link getSignatureKeys()} * function. * * For asymmetric keys, the public and private keys should have the same * signature. * * @return string the signature */ public function getSignature() { $keys = $this->getSignatureKeys(); $signing = array(); foreach ($keys as $key) { $signing[$key] = $this->data[$key]; } ksort($signing); return Util::base64url_encode(hash('sha256', json_encode($signing), true)); }
/** * Creates an EC key. * * The supported formats are: * * - `php` - JSON web key formatted as a PHP associative array * - `json` - JSON web key * - `jwe` - Encrypted JSON web key * - `pem` - the public or private key encoded in PEM (base64 encoded DER) format * * @param string|array $data the key data * @param string $format the format * @param string $password the password, if the key is password protected * @param string $alg the algorithm, if the key is password protected */ public function __construct($data, $format, $password = null, $alg = 'PBES2-HS256+A128KW') { switch ($format) { case 'php': case 'json': case 'jwe': parent::__construct($data, $format, $password, $alg); break; case 'pem': $offset = 0; $jwk = array(); if (preg_match(Key::PEM_PUBLIC, $data, $matches)) { $der = base64_decode($matches[1]); if ($der === FALSE) { throw new KeyException('Cannot read PEM key'); } $offset += ASN1::readDER($der, $offset, $value); // SEQUENCE $offset += ASN1::readDER($der, $offset, $value); // SEQUENCE $offset += ASN1::readDER($der, $offset, $algorithm); // OBJECT IDENTIFIER - AlgorithmIdentifier $algorithm = ASN1::decodeOID($algorithm); if ($algorithm != self::EC_OID) { throw new KeyException('Not EC key'); } $offset += ASN1::readDER($der, $offset, $curve); // OBJECT IDENTIFIER - parameters $curve = ASN1::decodeOID($curve); if (!isset(self::$curves, $curve)) { throw new KeyException('Unrecognised EC parameter: ' . $curve); } $len = self::$curves[$curve]['len']; $offset += ASN1::readDER($der, $offset, $point); // BIT STRING - ECPoint if (strlen($point) != $len + 1) { throw new KeyException('Incorrect public key length: ' . strlen($point)); } if (ord($point[0]) != 0x4) { throw new KeyException('Invalid public key'); } // W $x = substr($point, 1, $len / 2); $y = substr($point, 1 + $len / 2); $jwk['kty'] = self::KTY; $jwk['crv'] = self::$curves[$curve]['crv']; $jwk['x'] = Util::base64url_encode($x); $jwk['y'] = Util::base64url_encode($y); } elseif (preg_match(self::PEM_PRIVATE, $data, $matches)) { $der = base64_decode($matches[1]); if ($der === FALSE) { throw new KeyException('Cannot read PEM key'); } $offset += ASN1::readDER($der, $offset, $data); // SEQUENCE $offset += ASN1::readDER($der, $offset, $version); // INTEGER if (ord($version) != 1) { throw new KeyException('Invalid private key version'); } $offset += ASN1::readDER($der, $offset, $d); // OCTET STRING [d] $offset += ASN1::readDER($der, $offset, $data); // SEQUENCE[0] $offset += ASN1::readDER($der, $offset, $curve); // OBJECT IDENTIFIER - parameters $curve = ASN1::decodeOID($curve); if (!isset(self::$curves, $curve)) { throw new KeyException('Unrecognised EC parameter: ' . $curve); } $len = self::$curves[$curve]['len']; $offset += ASN1::readDER($der, $offset, $data); // SEQUENCE[1] $offset += ASN1::readDER($der, $offset, $point); // BIT STRING - ECPoint if (strlen($point) != $len + 1) { throw new KeyException('Incorrect private key length: ' . strlen($point)); } if (ord($point[0]) != 0x4) { throw new KeyException('Invalid private key'); } // W $x = substr($point, 1, ($len - 1) / 2); $y = substr($point, 1 + ($len - 1) / 2); $jwk['kty'] = self::KTY; $jwk['crv'] = self::$curves[$curve]['crv']; $jwk['d'] = Util::base64url_encode($d); $jwk['x'] = Util::base64url_encode($x); $jwk['y'] = Util::base64url_encode($y); } parent::__construct($jwk); break; default: throw new KeyException('Incorrect format'); } if (!isset($this->data['kty'])) { $this->data['kty'] = self::KTY; } }
public function encryptKey($cek, $keys, &$headers, $kid = null) { $key = $this->selectKey($keys, $kid, array(Key::PUBLIC_PROPERTY => true, '~use' => 'enc')); if ($key == null || !$key->isPublic()) { throw new CryptException('Key not found or is invalid'); } $headers['kid'] = $key->getKeyId(); $params = self::$alg_params[$this->getAlg()]; if (isset($params['oaep'])) { // $key->getSize() ignores the first octet when calculating the key size, // therefore we need to add it back in $cek = $this->oaep_encode($cek, 1 + $key->getSize() / 8, $params['oaep']); } $ciphertext = ''; if (!openssl_public_encrypt($cek, $ciphertext, $key->toPEM(), $params['padding'])) { $messages = array(); while ($message = openssl_error_string()) { $messages[] = $message; } throw new CryptException('Cannot encrypt key: ' . implode("\n", $messages)); } return Util::base64url_encode($ciphertext); }
public function shortHash($data) { $hash = hash('sha' . $this->size, $data, true); $short = substr($hash, 0, $this->size / 16); return Util::base64url_encode($short); }
protected function hex2base64url($hex) { return Util::base64url_encode(pack('H*', $hex)); }