Skip to content

Commit

Permalink
Tag Length and Appended Tag support
Browse files Browse the repository at this point in the history
  • Loading branch information
Florent Morselli committed Nov 14, 2016
1 parent c88ed75 commit b6d2896
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 9 deletions.
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
AES GCM (Galois Counter Mode) PHP Implementation
====================================================

Help me out for a couple of :beers:!

[![Beerpay](https://beerpay.io/Spomky-Labs/php-aes-gcm/badge.svg?style=beer-square)](https://beerpay.io/Spomky-Labs/php-aes-gcm) [![Beerpay](https://beerpay.io/Spomky-Labs/php-aes-gcm/make-wish.svg?style=flat-square)](https://beerpay.io/Spomky-Labs/php-aes-gcm?focus=wish)

----

[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/Spomky-Labs/php-aes-gcm/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/Spomky-Labs/php-aes-gcm/?branch=master)
[![Coverage Status](https://coveralls.io/repos/github/Spomky-Labs/php-aes-gcm/badge.svg?branch=master)](https://coveralls.io/github/Spomky-Labs/php-aes-gcm?branch=master)

Expand Down Expand Up @@ -37,6 +43,8 @@ composer require "spomky-labs/php-aes-gcm"
# How to use

```php
<?php

use AESGCM\AESGCM;

// The Key Encryption Key
Expand All @@ -61,6 +69,55 @@ $P = AESGCM::decrypt($K, $IV, $C, $A, $T);
// The value of $P should be hex2bin('d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39')
```

## Appended Tag

Some implementations of this cypher may append the tag at the end of the ciphertext.
This is commonly used by the Java implementation for example.

This library provides an easy way to produce such a ciphertext and read it.

```php
<?php

use AESGCM\AESGCM;

// The values $K, $P, $A, $IV hereafter have the same meaning as above

// $C is the encrypted data with the appended tag
$C = AESGCM::encryptAndAppendTag($K, $IV, $P, $A);
// The value of $C should be hex2bin('3980ca0b3c00e841eb06fac4872a2757859e1ceaa6efd984628593b40ca1e19c7d773d00c144c525ac619d18c84a3f4718e2448b2fe324d9ccda27102519498e80f1478f37ba55bd6d27618c')

$P = AESGCM::decryptWithAppendedTag($K, $IV, $C, $A);
// The value of $P should be hex2bin('d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39')
```

## Tag Length

By default the tag length is 128 bits. This value is highly recommended, however you may need to use another tag length.
As per the cypher specification, the tag length could be 128, 120, 112, 104 or 96 bits.

```php
<?php

use AESGCM\AESGCM;

// The values $K, $P, $A, $IV hereafter have the same meaning as above
$TL = 96; // In this example the tag length will be 96 bits

list($C, $T) = AESGCM::encrypt($K, $IV, $P, $A, $TL);
// The value of $C should be hex2bin('3980ca0b3c00e841eb06fac4872a2757859e1ceaa6efd984628593b40ca1e19c7d773d00c144c525ac619d18c84a3f4718e2448b2fe324d9ccda2710')
// The value of $T should be hex2bin('2519498e80f1478f37ba55bd')
```

The tag length is automatically calculated during the decryption operation with the method `AESGCM::decrypt`.
However, if the tag is appended at the end of the ciphertext and if it is not 128 bits, then it must be set:

```php
<?php

$P = AESGCM::decryptWithAppendedTag($K, $IV, $C, $A, $TL);
```

# Contributing

Requests for new features, bug fixed and all other ideas to make this library useful are welcome.
Expand Down
72 changes: 63 additions & 9 deletions src/AESGCM.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,50 @@
final class AESGCM
{
/**
* @param string $K Key encryption key
* @param string $IV Initialization vector
* @param null|string $P Data to encrypt (null for authentication)
* @param null|string $A Additional Authentication Data
* @param string $K Key encryption key
* @param string $IV Initialization vector
* @param null|string $P Data to encrypt (null for authentication)
* @param null|string $A Additional Authentication Data
* @param int $tag_length Tag length
*
* @return array
*/
public static function encrypt($K, $IV, $P, $A)
public static function encrypt($K, $IV, $P = null, $A = null, $tag_length = 128)
{
Assertion::string($K, 'The key encryption key must be a binary string.');
Assertion::string($IV, 'The Initialization Vector must be a binary string.');
Assertion::nullOrString($P, 'The data to encrypt must be null or a binary string.');
Assertion::nullOrString($A, 'The Additional Authentication Data must be null or a binary string.');
Assertion::integer($tag_length, 'Invalid tag length. Supported values are: 128, 120, 112, 104 and 96.');
Assertion::inArray($tag_length, [128, 120, 112, 104, 96], 'Invalid tag length. Supported values are: 128, 120, 112, 104 and 96.');
list($J0, $v, $a_len_padding, $H) = self::common($K, $IV, $A);

$C = self::getGCTR($K, self::getInc(32, $J0), $P);
$u = self::calcVector($C);
$c_len_padding = self::addPadding($C);

$S = self::getHash($H, $A.str_pad('', $v / 8, "\0").$C.str_pad('', $u / 8, "\0").$a_len_padding.$c_len_padding);
$T = self::getMSB(128, self::getGCTR($K, $J0, $S));
$T = self::getMSB($tag_length, self::getGCTR($K, $J0, $S));

return [$C, $T];
}

/**
* This method will append the tag at the end of the ciphertext.
*
* @param string $K Key encryption key
* @param string $IV Initialization vector
* @param null|string $P Data to encrypt (null for authentication)
* @param null|string $A Additional Authentication Data
* @param int $tag_length Tag length
*
* @return string
*/
public static function encryptAndAppendTag($K, $IV, $P = null, $A = null, $tag_length = 128)
{
return implode(self::encrypt($K, $IV, $P, $A, $tag_length));
}

/**
* @param string $K Key encryption key
* @param string $IV Initialization vector
Expand All @@ -46,8 +69,16 @@ public static function encrypt($K, $IV, $P, $A)
*
* @return string
*/
public static function decrypt($K, $IV, $C, $A, $T)
public static function decrypt($K, $IV, $C = null, $A = null, $T)
{
Assertion::string($K, 'The key encryption key must be a binary string.');
Assertion::string($IV, 'The Initialization Vector must be a binary string.');
Assertion::nullOrString($C, 'The data to encrypt must be null or a binary string.');
Assertion::nullOrString($A, 'The Additional Authentication Data must be null or a binary string.');

$tag_length = self::getLength($T);
Assertion::integer($tag_length, 'Invalid tag length. Supported values are: 128, 120, 112, 104 and 96.');
Assertion::inArray($tag_length, [128, 120, 112, 104, 96], 'Invalid tag length. Supported values are: 128, 120, 112, 104 and 96.');
list($J0, $v, $a_len_padding, $H) = self::common($K, $IV, $A);

$P = self::getGCTR($K, self::getInc(32, $J0), $C);
Expand All @@ -56,13 +87,36 @@ public static function decrypt($K, $IV, $C, $A, $T)
$c_len_padding = self::addPadding($C);

$S = self::getHash($H, $A.str_pad('', $v / 8, "\0").$C.str_pad('', $u / 8, "\0").$a_len_padding.$c_len_padding);
$T1 = self::getMSB(self::getLength($T), self::getGCTR($K, $J0, $S));
$T1 = self::getMSB($tag_length, self::getGCTR($K, $J0, $S));
$result = strcmp($T, $T1);
Assertion::eq($result, 0, 'Unable to decrypt or to verify the tag.');

return $P;
}

/**
* This method should be used if the tag is appended at the end of the ciphertext.
* It is used by some AES GCM implementations such as the Java one.
*
* @param string $K Key encryption key
* @param string $IV Initialization vector
* @param string|null $Ciphertext Data to encrypt (null for authentication)
* @param string|null $A Additional Authentication Data
* @param int $tag_length Tag length
*
* @return string
*
* @see self::encryptAndAppendTag
*/
public static function decryptWithAppendedTag($K, $IV, $Ciphertext = null, $A = null, $tag_length = 128)
{
$tag_length_in_bits = $tag_length/8;
$C = mb_substr($Ciphertext, 0, -$tag_length_in_bits, '8bit');
$T = mb_substr($Ciphertext, -$tag_length_in_bits, null, '8bit');

return self::decrypt($K, $IV, $C, $A, $T);
}

/**
* @param $K
* @param $IV
Expand All @@ -73,7 +127,7 @@ public static function decrypt($K, $IV, $C, $A, $T)
private static function common($K, $IV, $A)
{
$key_length = mb_strlen($K, '8bit') * 8;
Assertion::inArray($key_length, [128, 192, 256], 'Bad key length.');
Assertion::inArray($key_length, [128, 192, 256], 'Bad key encryption key length.');

$H = openssl_encrypt(str_repeat("\0", 16), 'aes-'.($key_length).'-ecb', $K, OPENSSL_NO_PADDING | OPENSSL_RAW_DATA); //---
$iv_len = self::getLength($IV);
Expand Down
7 changes: 7 additions & 0 deletions tests/IEEE802Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ public function testVectors($K, $P, $A, $IV, $expected_C, $expected_T)
$computed_P = AESGCM::decrypt($K, $IV, $C, $A, $T);

$this->assertEquals($P, $computed_P);

foreach([128, 120, 112, 104, 96] as $tag_length) {

$c = AESGCM::encryptAndAppendTag($K, $IV, $P, $A, $tag_length);
$p = AESGCM::decryptWithAppendedTag($K, $IV, $c, $A, $tag_length);
$this->assertEquals($P, $p);
}
}

public function dataVectors()
Expand Down
7 changes: 7 additions & 0 deletions tests/NistTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ public function testVectors($K, $P, $A, $IV, $expected_C, $expected_T)
$computed_P = AESGCM::decrypt($K, $IV, $C, $A, $T);

$this->assertEquals($P, $computed_P);

foreach([128, 120, 112, 104, 96] as $tag_length) {

$c = AESGCM::encryptAndAppendTag($K, $IV, $P, $A, $tag_length);
$p = AESGCM::decryptWithAppendedTag($K, $IV, $c, $A, $tag_length);
$this->assertEquals($P, $p);
}
}

public function dataVectors()
Expand Down

0 comments on commit b6d2896

Please sign in to comment.