Skip to content

Commit

Permalink
Add support for CCM mode
Browse files Browse the repository at this point in the history
  • Loading branch information
fpoirotte committed Jul 23, 2017
1 parent 220faf3 commit 06377d0
Show file tree
Hide file tree
Showing 3 changed files with 317 additions and 0 deletions.
147 changes: 147 additions & 0 deletions src/Cryptal/Modes/CCM.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
<?php

namespace fpoirotte\Cryptal\Modes;

use fpoirotte\Cryptal\Implementers\CryptoInterface;
use fpoirotte\Cryptal\AsymmetricModeInterface;

/**
* Counter with CBC-MAC (CCM)
*/
class CCM implements AsymmetricModeInterface
{
/// Cipher
protected $cipher;

/// Nonce
protected $nonce;

/// Length parameter
protected $L;

/// Output tag length
protected $M;

public function __construct(CryptoInterface $cipher, $iv, $tagLength)
{
if (16 !== $cipher->getBlockSize()) {
throw new \InvalidArgumentException('Incompatible cipher (block size != 16)');
}

$nonceSize = strlen($iv);
if ($nonceSize < 7 || $nonceSize > 13) {
throw new \Exception("Invalid nonce (should be between 7 and 13 bytes long)");
}

if (($tagLength & 0x1) || $tagLength < 4 || $tagLength > 16) {
throw new \Exception("Invalid tag length (valid values: 4, 6, 8, 10, 12, 14 & 16)");
}

$this->cipher = $cipher;
$this->nonce = $iv;
$this->L = 15 - $nonceSize;
$this->M = ($tagLength - 2) >> 1;
}

protected function checkum($M, $A)
{
$len = strlen($M);
for ($i = 0; $i < $this->L; $i++) {
$len >>= 8;
}
if ($len > 0) {
throw new \InvalidArgumentException('Invalid length for input data (greater than 2**8L)');
}

// Build the first block.
// Note: l(m) is < 2**32 in this implementation
$lenA = strlen($A);
$lenM = str_pad(ltrim(pack('N', strlen($M)), "\x00"), $this->L, "\x00", STR_PAD_LEFT);
$b = chr((($lenA > 0) << 6) | ($this->M << 3) | ($this->L - 1)) . $this->nonce . $lenM;

// Encode "l(a)"
if ($lenA < ((1 << 16) - (1 << 8))) {
// 0 < l(a) < 2**16 - 2**8
$b .= pack("n", $lenA);
} elseif (($lenA >> 32) === 0) {
// 2**16 - 2**8 <= l(a) < 2**32
$b .= pack("nN", 0xFEFF, $lenA);
} else {
// Messages with l(a) >= 2**32 are not supported yet
throw new \RuntimeException('Not implemented yet');
}

// Encode "a" and add padding if necessary
$b .= $A;
$b .= str_repeat("\x00", (16 - (strlen($b) % 16)) % 16);

// Encode "m" and add padding if necessary
$b .= $M;
$b .= str_repeat("\x00", (16 - (strlen($b) % 16)) % 16);

// Compute the checksum "X"
$X = str_repeat("\x00", 16);
foreach (str_split($b, 16) as $block) {
$X = $this->cipher->encrypt('', $X ^ $block);
}
$T = substr($X, 0, ($this->M << 1) + 2);
return $T;
}

/// Increment the value of the counter by one.
protected function incrementCounter($c)
{
for ($i = $this->L - 1; $i >= 0; $i--) {
// chr() takes care of overflows automatically.
$c[$i] = chr(ord($c[$i]) + 1);

// Stop, unless the incremented generated an overflow.
// In that case, we continue to propagate the carry.
if ("\x00" !== $c[$i]) {
break;
}
}
return $c;
}


public function encrypt($data, $context)
{
$options = stream_context_get_options($context);
$Adata = isset($options['cryptal']['data']) ? (string) $options['cryptal']['data'] : '';
$counter = str_repeat("\x00", $this->L);
$a = chr($this->L - 1) . $this->nonce; // Flags & nonce
$S0 = $this->cipher->encrypt('', $a . $counter);

$res = '';
foreach (str_split($data, 16) as $block) {
$counter = $this->incrementCounter($counter);
$res .= $block ^ $this->cipher->encrypt('', $a . $counter);
}

stream_context_set_option($context, 'cryptal', 'tag', $this->checkum($data, $Adata) ^ $S0);
return $res;
}

public function decrypt($data, $context)
{
$options = stream_context_get_options($context);
$Adata = isset($options['cryptal']['data']) ? (string) $options['cryptal']['data'] : '';
$T = isset($options['cryptal']['tag']) ? (string) $options['cryptal']['tag'] : '';
$counter = str_repeat("\x00", $this->L);
$a = chr($this->L - 1) . $this->nonce; // Flags & nonce
$S0 = $this->cipher->encrypt('', $a . $counter);

$res = '';
foreach (str_split($data, 16) as $block) {
$counter = $this->incrementCounter($counter);
$res .= $block ^ $this->cipher->encrypt('', $a . $counter);
}

$T2 = $this->checkum($res, $Adata) ^ $S0;
if ($T2 !== $T) {
throw new \InvalidArgumentException('Tag does not match expected value');
}
return $res;
}
}
121 changes: 121 additions & 0 deletions tests/api/CCMTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

use fpoirotte\Cryptal\Padding\None;
use fpoirotte\Cryptal\CipherEnum;
use fpoirotte\Cryptal\ModeEnum;

/**
* Test point addition for various NIST curves
* using the test vectors at http://point-at-infinity.org/ecc/nisttv
*/
class CCMTest extends \PHPUnit\Framework\TestCase
{
static $cache;

public static function setUpBeforeClass()
{
self::$cache = array();
}

public function getCipher($key)
{
if (!isset(self::$cache[$key])) {
$map = array(
16 => CipherEnum::CIPHER_AES_128(),
);
$cipher = new AesEcbStub($map[strlen($key)], ModeEnum::MODE_ECB(), new None, $key);
self::$cache[$key] = $cipher;
}
return self::$cache[$key];
}

public function vectors()
{
// K, N, A, P, C, T
return array(
// Test vectors from RFC 3610
array(
'C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF',
'00000003020100A0A1A2A3A4A5',
'0001020304050607',
'08090A0B0C0D0E0F101112131415161718191A1B1C1D1E',
'588C979A61C663D2F066D0C2C0F989806D5F6B61DAC384',
'17E8D12CFDF926E0',
),
array(
'C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF',
'00000004030201A0A1A2A3A4A5',
'0001020304050607',
'08090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F',
'72C91A36E135F8CF291CA894085C87E3CC15C439C9E43A3B',
'A091D56E10400916',
),
array(
'C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF',
'00000005040302A0A1A2A3A4A5',
'0001020304050607',
'08090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20',
'51B1E5F44A197D1DA46B0F8E2D282AE871E838BB64DA859657',
'4ADAA76FBD9FB0C5',
),
array(
'C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF',
'00000006050403A0A1A2A3A4A5',
'000102030405060708090A0B',
'0C0D0E0F101112131415161718191A1B1C1D1E',
'A28C6865939A9A79FAAA5C4C2A9D4A91CDAC8C',
'96C861B9C9E61EF1',
),
array(
'C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF',
'00000007060504A0A1A2A3A4A5',
'000102030405060708090A0B',
'0C0D0E0F101112131415161718191A1B1C1D1E1F',
'DCF1FB7B5D9E23FB9D4E131253658AD86EBDCA3E',
'51E83F077D9C2D93',
),
array(
'C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF',
'00000008070605A0A1A2A3A4A5',
'000102030405060708090A0B',
'0C0D0E0F101112131415161718191A1B1C1D1E1F20',
'6FC1B011F006568B5171A42D953D469B2570A4BD87',
'405A0443AC91CB94',
),
array(
'C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF',
'00000009080706A0A1A2A3A4A5',
'0001020304050607',
'08090A0B0C0D0E0F101112131415161718191A1B1C1D1E',
'0135D1B2C95F41D5D1D4FEC185D166B8094E999DFED96C',
'048C56602C97ACBB7490',
),
);
}

/**
* @dataProvider vectors
* @group medium
*/
public function testCCM($K, $N, $A, $P, $C, $T)
{
$K = pack('H*', $K);
$P = pack('H*', $P);
$A = pack('H*', $A);
$N = pack('H*', $N);
$C = strtolower($C);
$T = strtolower($T);

$cipher = $this->getCipher($K);
$ccm = new fpoirotte\Cryptal\Modes\CCM($cipher, $N, strlen($T) >> 1);
$ctx = stream_context_create(array('cryptal' => array('data' => $A)));

$res = $ccm->encrypt($P, $ctx);
$options = stream_context_get_options($ctx);
$this->assertSame($C, bin2hex($res));
$this->assertSame($T, bin2hex($options['cryptal']['tag']));

$res = $ccm->decrypt($res, $ctx);
$this->assertSame(bin2hex($P), bin2hex($res));
}
}
49 changes: 49 additions & 0 deletions tests/api/aes_ecb/c0c1c2c3c4c5c6c7c8c9cacbcccdcecf.dat
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
0100000003020100a0a1a2a3a4a50000 3a2e46c8ec33a5485620542c022cc07d
0100000003020100a0a1a2a3a4a50001 50859d916dcb6ddde077c2d1d4ec9f97
0100000003020100a0a1a2a3a4a50002 7546717ac6de9aff640c9c06de6d0d8f
5900000003020100a0a1a2a3a4a50017 eb9d5547730955ab231e0a2dfe4b90d6
eb955546710a51ae25190a2dfe4b90d6 cdb6411e3cdc9b4f5d9258b69ee7f091
c5bf4b1530d195404d834aa58af2e686 9c38405ea03c1bc904b58b40c76ca2eb
84215a45bc2105c904b58b40c76ca2eb 2dc697e411ca83a860c2c406ccaa542f
0100000009080706a0a1a2a3a4a50000 8d07802562b08c00a6eead29752b0add
0100000009080706a0a1a2a3a4a50001 093cdbb9c5524fdac1c5ecd291c470af
0100000009080706a0a1a2a3a4a50002 11578386e2c472b48ecc8aadab776fcb
0100000004030201a0a1a2a3a4a50000 5728d00496d265e556a857c1025f93af
0100000004030201a0a1a2a3a4a50001 7ac0103ded38f6c0390dba871c4991f4
0100000004030201a0a1a2a3a4a50002 d40cde22d5f92424f7be9a569da79f51
5900000004030201a0a1a2a3a4a50018 f0c254d3ca03e23970bd24a84c399e77
0100000005040302a0a1a2a3a4a50000 396ec01a7db96e6fa12aa238c0295bf5
0100000005040302a0a1a2a3a4a50001 59b8efff46147312b47a1d9d393d3cff
0100000005040302a0a1a2a3a4a50002 69f122a078c79b8977894c99975c2378
6100000009080706a0a1a2a3a4a50017 6006c572da239cbfa05b0aded2cda81e
600ec573d82098baa65c0aded2cda81e 417de2ae94e2ead900fc44fcd0695227
4974e8a598efe4d610ed56efc47c4430 2a6c42ca49d7c701c57d59ff8716490e
327558d155cad901c57d59ff8716490e 898bd6454e2720bbd27ef3157a7c90b2
0100000008070605a0a1a2a3a4a50000 e57ddc56c652922b6a1ab87baa923b99
0100000008070605a0a1a2a3a4a50001 63ccbe1ee01744984564b23a8d245c80
0100000008070605a0a1a2a3a4a50002 396dbaa2a7d2cbd4b5e17c107945bbc0
5900000008070605a0a1a2a3a4a50015 0472da4c6ff60a6306521a060480cde5
047eda4d6df50e660055120f0e8bcde5 644c36a5a22737620b89f1d7bff273d4
684138aab23625711f9ce7c0a7eb69cf 41e119cd1924ce77f12fa660c16ebb4e
5dfc07d23924ce77f12fa660c16ebb4e a527d8156ac359bf1cb886e62f299129
0100000007060504a0a1a2a3a4a50000 1951d78528996726b001fa7ceca6dcad
0100000007060504a0a1a2a3a4a50001 d0fcf5744d8f31e8895b05054b7c90c3
0100000007060504a0a1a2a3a4a50002 72a0d4219f0de1d40483bc2d3d0cfc2a
5900000007060504a0a1a2a3a4a50014 004c509545803c4851cde13b56c89a85
004050944783384d57cae9325cc39a85 e2b8f7ce49b2217284a8ea84faad675c
eeb5f9c159a3336190bdfc93e2b47d47 3efb367225db1101d3c22f0ecaff44f3
22e6286d25db1101d3c22f0ecaff44f3 48b9e88255054ab5490a95f9349b4b5e
f0ca54d2c800e63c76ba24a84c399e77 48de8b8628ea4a4000aa42c295bf4a8c
40d7818d24e7444f10bb50d181aa5c9b 0f89ffbca62bc24f13215f168796aa33
1790e5a7ba36dc5013215f168796aa33 f7b9056a86926cf3fb163dc499efaa11
5900000005040302a0a1a2a3a4a50019 6f8a12f7bf8d4dc5a1196e95dff0b427
6f8212f6bd8e49c0a71e6e95dff0b427 37e9b78cc22017e73380430cbef42824
3fe0bd87ce2d19e82391511faae13e33 90ca05139f4d4ecf226fe981c59e2d40
88d31f08835050d0026fe981c59e2d40 73b46775c026deaa410397d670fe5fb0
0100000006050403a0a1a2a3a4a50000 dd872a807c75f84e296bb883a30ce22f
0100000006050403a0a1a2a3a4a50001 ae81666a838b886aeebf4a5b3284508a
0100000006050403a0a1a2a3a4a50002 d1b19206ac939e2fb6ddce10a774fd8d
5900000006050403a0a1a2a3a4a50013 06652c600ef58963cac325a9cd3e2be1
06692c610cf68d66ccc42da0c7352be1 a07509ac15c25886042f806054fea686
ac7807a305d34a95103a96774ce7bc9d 644c0990d91b83e9ab4b8eed066ff5bf
78511790d91b83e9ab4b8eed066ff5bf 4b4f4b39b593e6bfb0b2c2b70f29cd7a

0 comments on commit 06377d0

Please sign in to comment.