<?php # Vernam Cipher (One-time pad)$k1='1.key';$k2='2.key';$d='The quick brown fox jumped over the lazy dog';VernamCipher::createTestKeyFile($k1,1024);copy($k1,$k2);$c1= newVernamCipher($k1);$eD=$c1->encryptWithHMAC($d);
echo'Encrypted: ',bin2hex($eD);$c2= newVernamCipher($k2);
echoPHP_EOL,'Decrypted: ',$c2->decryptWithHMAC($eD);
classVernamCipher{
constDEFAULT_HMAC_ALGO='sha3-256';
constDEFAULT_HMAC_KEY_LENGTH=16;
constDEFAULT_HMAC_HASH_LENGTH=32;
private$keyFilePath;
private$keyFileHandler;
private$deferredFtruncate=false;
private$deferredFtruncatePos;
private$hmacAlgo=self::DEFAULT_HMAC_ALGO;
private$hmacKeyLength=self::DEFAULT_HMAC_KEY_LENGTH;
private$hmacHashLength=self::DEFAULT_HMAC_HASH_LENGTH;
function__construct(string $keyFilePath,string $hmacAlgo=self::DEFAULT_HMAC_ALGO,int $hmacKeyLength=self::DEFAULT_HMAC_KEY_LENGTH)
{$this->keyFilePath=$keyFilePath;$this->openKeyFile();
if($hmacAlgo!==self::DEFAULT_HMAC_ALGO) {
if(in_array($hmacAlgo,hash_algos())) {$this->hmacAlgo=$hmacAlgo;$this->hmacHashLength=strlen(hash($this->hmacAlgo,'',true));
}
else {$this->hmacAlgo=self::DEFAULT_HMAC_ALGO;$this->hmacHashLength=self::DEFAULT_HMAC_HASH_LENGTH;
}
}
if($hmacKeyLength!==self::DEFAULT_HMAC_KEY_LENGTH) {$this->hmacKeyLength=$hmacKeyLength;
}
}
public functionencryptWithHMAC(string $data)
{$hmacKey=$this->getBytes($this->hmacKeyLength);$encData=$this->encrypt($data);$dataHmac=$this->hashHmac($encData,$hmacKey);
return$dataHmac.$encData;
}
public functiondecryptWithHMAC(string $data)
{$dataLength=strlen($data);
if($dataLengthhmacHashLength)
throw newException('data length '.$dataLength.' < hmac length '.$this->hmacHashLength);$dataHmacRemote=substr($data,0,$this->hmacHashLength);$dataOnly=substr($data,$this->hmacHashLength);$hmacKey=$this->getBytes($this->hmacKeyLength,false);$dataHmacLocal=$this->hashHmac($dataOnly,$hmacKey);
if(hash_equals($dataHmacLocal,$dataHmacRemote) ===false)
throw newException('Hashes not equals, remote: '.bin2hex($dataHmacRemote).' local:'.bin2hex($dataHmacLocal));$this->deferredFtruncate();
return$this->encrypt($dataOnly);
}
public functionencrypt(string $data) :string{$dataLength=strlen($data);$bytes=$this->getBytes($dataLength);
for($i=0;$i
return$data;
}
public functiondecrypt(string $data) :string{
return$this->encrypt($data);
}
private functionhashHmac($data,$key)
{
returnhash_hmac($this->hmacAlgo,$data,$key,true);
}# Don't use in production. You must use true random number generator.public static functioncreateTestKeyFile(string $filePath,int $size)
{file_put_contents($filePath,random_bytes($size));
}
private functiondeferredFtruncate()
{
if(!$this->deferredFtruncate)
return;ftruncate($this->keyFileHandler,$this->deferredFtruncatePos);$this->deferredFtruncate=false;
}
public functiongetBytes(int $length,$truncateNow=true) :string{fseek($this->keyFileHandler,0,SEEK_END);$currentPos=ftell($this->keyFileHandler);
if($currentPos
throw newException('Not enough key materials, key size: '.$currentPos.' needed: '.$length);fseek($this->keyFileHandler, -$length,SEEK_END);$bytes=fread($this->keyFileHandler,$length);
if($truncateNow)ftruncate($this->keyFileHandler,$currentPos-$length);
else {$this->deferredFtruncate=true;$this->deferredFtruncatePos=$currentPos-$length;
}
return$bytes;
}
private functionopenKeyFile()
{
if($this->keyFileHandler)
return;
if(($this->keyFileHandler=fopen($this->keyFilePath,'rb+')) ===false)
throw newException('Cant open key file: '.$this->keyFilePath);
if(!flock($this->keyFileHandler,LOCK_EX|LOCK_NB))
throw newException('Cant lock key file: '.$this->keyFilePath);
}
}?>