Skip to content

Commit

Permalink
Merge pull request #9 from Spomky-Labs/v4.0
Browse files Browse the repository at this point in the history
* Assertion library removed
* All classes marked as final
* PHP 7.0 is now the minimal requirement
* PHP 7.0 Type hinting
* Tests using PHPUnit 6
* Performance tests
  • Loading branch information
Spomky authored Mar 26, 2017
2 parents 072e312 + a0c8fbd commit fa6436d
Show file tree
Hide file tree
Showing 15 changed files with 268 additions and 136 deletions.
19 changes: 11 additions & 8 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
language: php

php:
- 5.4
- 5.5
- 5.6
- 7.0
- 7.1
- nightly
- hhvm
matrix:
allow_failures:
- php: hhvm
- php: nightly
fast_finish: true
include:
- php: 7.0
- php: 7.1
- php: hhvm
- php: nightly

before_script:
- composer self-update
Expand All @@ -19,3 +21,4 @@ script:

after_success:
- vendor/bin/coveralls --no-interaction
- php -r "require_once 'vendor/autoload.php'; AESKW\Tests\Performance::run();";
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,17 @@ The release process [is described here](doc/Release.md).

## Prerequisites

This library needs at least `PHP 5.4`.
This library needs at least `PHP 7.0`.
It has been successfully tested using `PHP 7.0`, `PHP 7.1` and `HHVM`.

It has been successfully tested using `PHP 5.4` to `PHP 5.6`, `PHP 7.0` and `HHVM`.
For `PHP 5.4+`, please use the version `3.x` of this library.

## Installation

The preferred way to install this library is to rely on Composer:

```sh
composer require "spomky-labs/aes-key-wrap" "^3.0"
composer require spomky-labs/aes-key-wrap
```

## How to use
Expand Down
7 changes: 3 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,17 @@
}
},
"require": {
"php": "^5.4|^7.0",
"php": "^7.0",
"ext-mbstring": "*",
"beberlei/assert": "^2.4",
"lib-openssl": "*"
},
"require-dev": {
"phpunit/phpunit": "^4.5|^5.0",
"phpunit/phpunit": "^6.0",
"satooshi/php-coveralls": "^1.0"
},
"extra": {
"branch-alias": {
"dev-master": "3.0.x-dev"
"dev-master": "4.0.x-dev"
}
}
}
10 changes: 4 additions & 6 deletions src/A128KW.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,15 @@

namespace AESKW;

use Assert\Assertion;

class A128KW
final class A128KW
{
use AESKW;

/**
* @param string $kek The Key Encryption Key
* {@inheritdoc}
*/
protected static function checkKEKSize($kek)
protected static function getExpectedKEKSize(): int
{
Assertion::eq(mb_strlen($kek, '8bit'), 16, 'Bad KEK size');
return 16;
}
}
10 changes: 4 additions & 6 deletions src/A192KW.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,15 @@

namespace AESKW;

use Assert\Assertion;

class A192KW
final class A192KW
{
use AESKW;

/**
* @param string $kek The Key Encryption Key
* {@inheritdoc}
*/
protected static function checkKEKSize($kek)
protected static function getExpectedKEKSize(): int
{
Assertion::eq(mb_strlen($kek, '8bit'), 24, 'Bad KEK size');
return 24;
}
}
10 changes: 4 additions & 6 deletions src/A256KW.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,15 @@

namespace AESKW;

use Assert\Assertion;

class A256KW
final class A256KW
{
use AESKW;

/**
* @param string $kek The Key Encryption Key
* {@inheritdoc}
*/
protected static function checkKEKSize($kek)
protected static function getExpectedKEKSize(): int
{
Assertion::eq(mb_strlen($kek, '8bit'), 32, 'Bad KEK size');
return 32;
}
}
87 changes: 58 additions & 29 deletions src/AESKW.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@

namespace AESKW;

use Assert\Assertion;

trait AESKW
{
/**
Expand All @@ -27,7 +25,7 @@ trait AESKW
*
* @see https://tools.ietf.org/html/rfc3394#section-2.2.3.1
*/
private static function getInitialValue(&$key, $padding_enabled)
private static function getInitialValue(string &$key, bool $padding_enabled): string
{
if (false === $padding_enabled) {
return hex2bin('A6A6A6A6A6A6A6A6');
Expand All @@ -49,7 +47,7 @@ private static function getInitialValue(&$key, $padding_enabled)
*
* @return bool
*/
private static function checkInitialValue(&$key, $padding_enabled, $iv)
private static function checkInitialValue(string &$key, bool $padding_enabled, string $iv): bool
{
// RFC3394 compliant
if ($iv === hex2bin('A6A6A6A6A6A6A6A6')) {
Expand Down Expand Up @@ -88,10 +86,14 @@ private static function checkInitialValue(&$key, $padding_enabled, $iv)
* @param string $key The Key to wrap
* @param bool $padding_enabled
*/
private static function checkKeySize($key, $padding_enabled)
private static function checkKeySize(string $key, bool $padding_enabled)
{
Assertion::false(false === $padding_enabled && 0 !== mb_strlen($key, '8bit') % 8, 'Bad key size');
Assertion::greaterOrEqualThan(mb_strlen($key, '8bit'), 1, 'Bad key size');
if (empty($key)) {
throw new \InvalidArgumentException('Bad key size');
}
if (false === $padding_enabled && 0 !== mb_strlen($key, '8bit') % 8) {
throw new \InvalidArgumentException('Bad key size');
}
}

/**
Expand All @@ -101,7 +103,7 @@ private static function checkKeySize($key, $padding_enabled)
*
* @return string The wrapped key
*/
public static function wrap($kek, $key, $padding_enabled = false)
public static function wrap(string $kek, string $key, bool $padding_enabled = false): string
{
self::checkKEKSize($kek);
$A = self::getInitialValue($key, $padding_enabled);
Expand All @@ -110,16 +112,15 @@ public static function wrap($kek, $key, $padding_enabled = false)
$N = count($P);
$C = [];

$encryptor = self::getEncryptor($kek);
if (1 === $N) {
$B = $encryptor->encrypt($A.$P[0]);
$B = self::encrypt($kek, $A.$P[0]);
$C[0] = self::getMSB($B);
$C[1] = self::getLSB($B);
} elseif (1 < $N) {
$R = $P;
for ($j = 0; $j <= 5; ++$j) {
for ($i = 1; $i <= $N; ++$i) {
$B = $encryptor->encrypt($A.$R[$i - 1]);
$B = self::encrypt($kek, $A.$R[$i - 1]);
$t = $i + $j * $N;
$A = self::toXBits(64, $t) ^ self::getMSB($B);
$R[$i - 1] = self::getLSB($B);
Expand All @@ -138,26 +139,25 @@ public static function wrap($kek, $key, $padding_enabled = false)
*
* @return string The key unwrapped
*/
public static function unwrap($kek, $key, $padding_enabled = false)
public static function unwrap(string $kek, string $key, bool $padding_enabled = false): string
{
self::checkKEKSize($kek);
$P = str_split($key, 8);
$A = $P[0];
$N = count($P);

Assertion::greaterThan($N, 1, 'Bad data');
$encryptor = self::getEncryptor($kek);

if (2 === $N) {
$B = $encryptor->decrypt($P[0].$P[1]);
if (2 > $N) {
throw new \InvalidArgumentException('Bad data');
} elseif (2 === $N) {
$B = self::decrypt($kek, $P[0].$P[1]);
$unwrapped = self::getLSB($B);
$A = self::getMSB($B);
} else {
$R = $P;
for ($j = 5; $j >= 0; --$j) {
for ($i = $N - 1; $i >= 1; --$i) {
$t = $i + $j * ($N - 1);
$B = $encryptor->decrypt((self::toXBits(64, $t) ^ $A).$R[$i]);
$B = self::decrypt($kek, (self::toXBits(64, $t) ^ $A).$R[$i]);
$A = self::getMSB($B);
$R[$i] = self::getLSB($B);
}
Expand All @@ -166,18 +166,25 @@ public static function unwrap($kek, $key, $padding_enabled = false)

$unwrapped = implode('', $R);
}
Assertion::true(self::checkInitialValue($unwrapped, $padding_enabled, $A), 'Integrity check failed');
if (false === self::checkInitialValue($unwrapped, $padding_enabled, $A)) {
throw new \InvalidArgumentException('Integrity check failed!');
}

return $unwrapped;
}

/**
* @return int
*/
abstract protected static function getExpectedKEKSize(): int;

/**
* @param int $bits
* @param int $value
*
* @return string
*/
private static function toXBits($bits, $value)
private static function toXBits(int $bits, int $value): string
{
return hex2bin(str_pad(dechex($value), $bits / 4, '0', STR_PAD_LEFT));
}
Expand All @@ -187,7 +194,7 @@ private static function toXBits($bits, $value)
*
* @return string
*/
private static function getMSB($value)
private static function getMSB(string $value): string
{
return mb_substr($value, 0, mb_strlen($value, '8bit') / 2, '8bit');
}
Expand All @@ -197,22 +204,44 @@ private static function getMSB($value)
*
* @return string
*/
private static function getLSB($value)
private static function getLSB(string $value): string
{
return mb_substr($value, mb_strlen($value, '8bit') / 2, null, '8bit');
}

/**
* @param string $kek
*
* @return \AESKW\EncryptorInterface
* {@inheritdoc}
*/
private static function getEncryptor($kek)
private static function encrypt(string $kek, string $data): string
{
if (extension_loaded('openssl')) {
return new OpenSSLEncryptor($kek);
return openssl_encrypt($data, self::getMethod($kek), $kek, OPENSSL_ZERO_PADDING | OPENSSL_RAW_DATA);
}

/**
* {@inheritdoc}
*/
private static function decrypt(string $kek, string $data): string
{
return openssl_decrypt($data, self::getMethod($kek), $kek, OPENSSL_ZERO_PADDING | OPENSSL_RAW_DATA);
}

/**
* @param string $kek The Key Encryption Key
*/
private static function checkKEKSize(string $kek)
{
if (mb_strlen($kek, '8bit') !== self::getExpectedKEKSize()) {
throw new \InvalidArgumentException('Bad KEK size');
}
}

throw new \RuntimeException('Please install OpenSSL extension.');
/**
* @param string $kek
*
* @return string
*/
private static function getMethod(string $kek): string
{
return sprintf('aes-%d-ecb', mb_strlen($kek, '8bit') * 8);
}
}
29 changes: 0 additions & 29 deletions src/EncryptorInterface.php

This file was deleted.

Loading

0 comments on commit fa6436d

Please sign in to comment.