cryptal  latest
Cryptography Abstraction Layer
OCB.php
1 <?php
2 
4 
7 
16 class OCB implements AsymmetricModeInterface
17 {
19  protected $cipher;
20 
22  protected $key;
23 
25  protected $iv;
26 
28  protected $taglen;
29 
30  public function __construct(CryptoInterface $cipher, $iv, $tagLength)
31  {
32  if (16 !== $cipher->getBlockSize()) {
33  throw new \InvalidArgumentException('Incompatible cipher (block size != 16)');
34  }
35 
36  if ($tagLength > 16) {
37  throw new \InvalidArgumentException('Invalid tag length (must be in 0..16)');
38  }
39 
40  if (strlen($iv) > 15) {
41  throw new \InvalidArgumentException('Invalid nonce length (must be in 0..16)');
42  }
43 
44  $this->taglen = $tagLength;
45  $this->cipher = $cipher;
46  $this->iv = $iv;
47  $this->l = new \fpoirotte\Cryptal\Modes\OCB\Lseries($cipher);
48  }
49 
50  protected static function ntz($n)
51  {
52  return strcspn(strrev(decbin($n)), '1');
53  }
54 
55  protected function hash($A)
56  {
57  $m = strlen($A) >> 4; // number of 128-bit blocks in A
58  $Atail = strlen($A) % 16; // 0 if the last block is a full block
59  $A = str_split($A, 16);
60 
61  $Sum = $Offset = str_repeat("\x00", 16);
62  for ($i = 0; $i < $m; $i++) {
63  $Offset ^= $this->l[self::ntz($i + 1)];
64  $Sum ^= $this->cipher->encrypt('', $A[$i] ^ $Offset);
65  }
66 
67  if ($Atail) {
68  $Offset ^= $this->l['*'];
69  $CipherInput = str_pad($A[$m] . "\x80", 16, "\x00") ^ $Offset;
70  $Sum ^= $this->cipher->encrypt('', $CipherInput);
71  }
72 
73  return $Sum;
74  }
75 
76  public function encrypt($data, $context)
77  {
78  $options = stream_context_get_options($context);
79  $A = isset($options['cryptal']['data']) ? (string) $options['cryptal']['data'] : '';
80 
81  $m = strlen($data) >> 4; // number of 128-bit blocks in P
82  $Ptail = strlen($data) % 16; // 0 if the last block is a full block
83  $P = str_split($data, 16);
84 
85  $Nlen = strlen($this->iv);
86  $Nonce = sprintf("%07b", ($this->taglen << 3) % 128) . str_repeat('0', 120 - ($Nlen << 3)) . '1' .
87  vsprintf(str_repeat("%08b", $Nlen), array_map('ord', str_split($this->iv)));
88  $bottom = bindec(substr($Nonce, 122));
89  $Ktop = array_map('bindec', str_split(substr($Nonce, 0, 122) . '000000', 8));
90  $Ktop = $this->cipher->encrypt('', implode('', array_map('chr', $Ktop)));
91  $Stretch = $Ktop . (substr($Ktop, 0, 8) ^ substr($Ktop, 1, 8));
92  $Offset = vsprintf(str_repeat("%08b", 24), array_map('ord', str_split($Stretch)));
93  $Offset = substr($Offset, $bottom, 128);
94  $Offset = array_map('bindec', str_split($Offset, 8));
95  $Offset = implode('', array_map('chr', $Offset));
96  $Checksum = str_repeat("\x00", 16);
97 
98  $C = '';
99  for ($i = 0; $i < $m; $i++) {
100  $Offset ^= $this->l[self::ntz($i + 1)];
101  $C .= $Offset ^ $this->cipher->encrypt('', $P[$i] ^ $Offset);
102  $Checksum ^= $P[$i];
103  }
104 
105  if ($Ptail) {
106  $Offset ^= $this->l['*'];
107  $Pad = $this->cipher->encrypt('', $Offset);
108  $C .= $P[$m] ^ $Pad;
109  $Checksum ^= str_pad($P[$m] . "\x80", 16, "\x00");
110  }
111 
112  $Tag = $this->cipher->encrypt('', $Checksum ^ $Offset ^ $this->l['$']) ^ $this->hash($A);
113  stream_context_set_option($context, 'cryptal', 'tag', substr($Tag, 0, $this->taglen));
114  return $C;
115  }
116 
117  public function decrypt($data, $context)
118  {
119  $options = stream_context_get_options($context);
120  $A = isset($options['cryptal']['data']) ? (string) $options['cryptal']['data'] : '';
121  $T = isset($options['cryptal']['tag']) ? (string) $options['cryptal']['tag'] : '';
122 
123  $m = strlen($data) >> 4; // number of 128-bit blocks in C
124  $Ctail = strlen($data) % 16; // 0 if the last block is a full block
125  $C = str_split($data, 16);
126 
127  $Nlen = strlen($this->iv);
128  $Nonce = sprintf("%07b", ($this->taglen << 3) % 128) . str_repeat('0', 120 - ($Nlen << 3)) . '1' .
129  vsprintf(str_repeat("%08b", $Nlen), array_map('ord', str_split($this->iv)));
130  $bottom = bindec(substr($Nonce, 122));
131  $Ktop = array_map('bindec', str_split(substr($Nonce, 0, 122) . '000000', 8));
132  $Ktop = $this->cipher->encrypt('', implode('', array_map('chr', $Ktop)));
133  $Stretch = $Ktop . (substr($Ktop, 0, 8) ^ substr($Ktop, 1, 8));
134  $Offset = vsprintf(str_repeat("%08b", 24), array_map('ord', str_split($Stretch)));
135  $Offset = substr($Offset, $bottom, 128);
136  $Offset = array_map('bindec', str_split($Offset, 8));
137  $Offset = implode('', array_map('chr', $Offset));
138  $Checksum = str_repeat("\x00", 16);
139 
140  $P = '';
141  for ($i = 0; $i < $m; $i++) {
142  $Offset ^= $this->l[self::ntz($i + 1)];
143  $decoded = $Offset ^ $this->cipher->decrypt('', $C[$i] ^ $Offset);
144  $P .= $decoded;
145  $Checksum ^= $decoded;
146  }
147 
148  if ($Ctail) {
149  $Offset ^= $this->l['*'];
150  $Pad = $this->cipher->encrypt('', $Offset);
151  $Pstar = $C[$m] ^ $Pad;
152  $P .= $Pstar;
153  $Checksum ^= str_pad($Pstar . "\x80", 16, "\x00");
154  }
155 
156  $Tag = $this->cipher->encrypt('', $Checksum ^ $Offset ^ $this->l['$']) ^ $this->hash($A);
157  if ((string) substr($Tag, 0, $this->taglen) !== $T) {
158  throw new \InvalidArgumentException('Tag does not match expected value');
159  }
160 
161  return $P;
162  }
163 }
$taglen
Output tag length (in bytes)
Definition: OCB.php:28
decrypt($data, $context)
Definition: OCB.php:117
encrypt($data, $context)
Definition: OCB.php:76
$key
Secret key.
Definition: OCB.php:22
__construct(CryptoInterface $cipher, $iv, $tagLength)
Definition: OCB.php:30
$cipher
Approved block cipher with a 128-bit block size.
Definition: OCB.php:19
$iv
Initialization Vector.
Definition: OCB.php:25