<?php
namespace ParagonIE\CipherSweet;
use ParagonIE\CipherSweet\Backend\Key\SymmetricKey;
use ParagonIE\CipherSweet\Exception\BlindIndexNameCollisionException;
use ParagonIE\CipherSweet\Exception\BlindIndexNotFoundException;
use ParagonIE\CipherSweet\Exception\CryptoOperationException;
use ParagonIE\ConstantTime\Hex;
/**
* Class EncryptedField
* @package ParagonIE\CipherSweet
*/
class EncryptedField
{
/**
* @var array<string, BlindIndex> $blindIndexes
*/
protected $blindIndexes = [];
/**
* @var CipherSweet $engine
*/
protected $engine;
/**
* @var SymmetricKey $key
*/
protected $key;
/**
* @var string $fieldName
*/
protected $fieldName = '';
/**
* @var string $tableName
*/
protected $tableName = '';
/**
* EncryptedField constructor.
*
* @param CipherSweet $engine
* @param string $tableName
* @param string $fieldName
*
* @throws CryptoOperationException
*/
public function __construct(CipherSweet $engine, $tableName = '', $fieldName = '')
{
$this->engine = $engine;
$this->tableName = $tableName;
$this->fieldName = $fieldName;
$this->key = $this->engine->getFieldSymmetricKey(
$this->tableName,
$this->fieldName
);
}
/**
* Encrypt a value and calculate all of its blind indices in one go.
*
* @param string $plaintext
* @return array<int, string|array>
*
* @throws BlindIndexNotFoundException
* @throws CryptoOperationException
*/
public function prepareForStorage($plaintext)
{
return [
$this->encryptValue($plaintext),
$this->getAllBlindIndexes($plaintext)
];
}
/**
* Encrypt a single value, using the per-field symmetric key.
*
* @param string $plaintext
* @return string
*/
public function encryptValue($plaintext)
{
return $this
->engine
->getBackend()
->encrypt(
$plaintext,
$this->key
);
}
/**
* Decrypt a single value, using the per-field symmetric key.
*
* @param string $ciphertext
* @return string
*/
public function decryptValue($ciphertext)
{
return $this
->engine
->getBackend()
->decrypt(
$ciphertext,
$this->key
);
}
/**
* Get all blind index values for a given plaintext.
*
* @param string $plaintext
* @return array<string, string|array>
*
* @throws BlindIndexNotFoundException
* @throws CryptoOperationException
*/
public function getAllBlindIndexes($plaintext)
{
$output = [];
$key = $this->engine->getBlindIndexRootKey(
$this->tableName,
$this->fieldName
);
/** @var BlindIndex $index */
foreach ($this->blindIndexes as $name => $index) {
$k = $this->engine->getIndexTypeColumn(
$this->tableName,
$this->fieldName,
$name
);
$output[$name] = [
'type' => $k,
'value' =>
Hex::encode(
$this->getBlindIndexRaw(
$plaintext,
$name,
$key
)
)
];
}
return $output;
}
/**
* Get a particular blind index of a given plaintext.
*
* @param string $plaintext
* @param string $name
* @return array
*
* @throws BlindIndexNotFoundException
* @throws CryptoOperationException
*/
public function getBlindIndex($plaintext, $name)
{
$key = $this->engine->getBlindIndexRootKey(
$this->tableName,
$this->fieldName
);
$k = $this->engine->getIndexTypeColumn(
$this->tableName,
$this->fieldName,
$name
);
return [
'type' => $k,
'value' =>
Hex::encode(
$this->getBlindIndexRaw(
$plaintext,
$name,
$key
)
)
];
}
/**
* Internal: Get the raw blind index. Returns a raw binary string.
*
* @param string $plaintext
* @param string $name
* @param SymmetricKey|null $key
* @return string
*
* @throws BlindIndexNotFoundException
* @throws CryptoOperationException
*/
protected function getBlindIndexRaw(
$plaintext,
$name,
SymmetricKey $key = null
) {
if (!isset($this->blindIndexes[$name])) {
throw new BlindIndexNotFoundException(
'Blind index ' . $name . ' not found'
);
}
if (!$key) {
$key = $this->engine->getBlindIndexRootKey(
$this->tableName,
$this->fieldName
);
}
$backend = $this->engine->getBackend();
$subKey = new SymmetricKey(
$backend,
\hash_hmac(
'sha256',
Util::pack([$this->tableName, $this->fieldName, $name]),
$key->getRawKey(),
true
)
);
/** @var BlindIndex $index */
$index = $this->blindIndexes[$name];
if ($index->getFastHash()) {
return $backend->blindIndexFast(
$plaintext,
$subKey,
$index->getFilterBitLength()
);
}
return $backend->blindIndexSlow(
$plaintext,
$subKey,
$index->getFilterBitLength(),
$index->getHashConfig()
);
}
/**
* Get a list of all the blind index "type"s, corresponding with their
* index names.
*
* @return array<string, string>
*/
public function getBlindIndexTypes()
{
$typeArray = [];
foreach (\array_keys($this->blindIndexes) as $name) {
$typeArray[$name] = $this->engine->getIndexTypeColumn(
$this->tableName,
$this->fieldName,
$name
);
}
return $typeArray;
}
/**
* Add a blind index to this encrypted field.
*
* @param BlindIndex $index
* @param string|null $name
* @return self
* @throws BlindIndexNameCollisionException
*/
public function addBlindIndex(BlindIndex $index, $name = null)
{
if (\is_null($name)) {
$name = $index->getName();
}
if (isset($this->blindIndexes[$name])) {
throw new BlindIndexNameCollisionException(
'Index ' . $name . ' already defined'
);
}
$this->blindIndexes[$name] = $index;
return $this;
}
}
|