Tested with the following HSMs:
- SafeNET Luna SA 4
- SoftHSM 2.6
- Nitrokey HSM 2
- AWS CloudHSM
Supports PHP version >= 7.4
phpize
./configure
make
To make tests, ensure that SoftHSM2 is installed, configured and initialized.
- Get the source
- Extract Archive
cd /path/to/extracted/archive
./configure
make
make install
Module will be located in /usr/local/lib/softhsm/libsofthsm2.so
As of this writing, Ubuntu 20.04 provides v2.2 of SoftHSM2 in which not all mechanisms will be available
sudo apt install softhsm2
Module will be located in /usr/lib/softhsm/libsofthsm2.so
- Create a directory where HSM files will be stored
/home/user/.softhsm
- Create a configuration file in your home directory
~/.config/softhsm2/softhsm2.conf
- Initialize token
softhsm2-util --init-token --slot 0 --label "My token 1" --pin 123456 --so-pin 12345678
- Grab the resulting slot ID
softhsm2-util --show-slots
Example configuration file
directories.tokendir = /home/user/.softhsm
objectstore.backend = file
log.level = INFO
slots.removable = false
slots.mechanisms = ALL
export PHP11_MODULE=/path/to/libsofthsm2.so
export PHP11_SLOT={SLOT_ID_FROM_INITIALIZATION}
export PHP11_PIN=123456
make test
All examples assume the use of a locally compiled installation of SoftHSM, but it will work with other modules as well.
To load a PKCS11 module, create a new PKCS11\Module object.
$modulePath = '/path/to/libsofthsm2.so';
$module = new Pkcs11\Module($modulePath);
From the PKCS11\Module object, you can call most PKCS11 methods.
To get information the module:
$moduleInfo = $module->getInfo();
There are 2 methods to retrieve slots information. getSlotList
can be used to retrieve a simple list of slots like C_GetSlotList
would, while getSlots
returns de result of C_GetSlotInfo
for each available slot.
$slotList = $module->getSlotList();
$slots = $module->getSlots();
$slotId = $slotList[0];
$slotInfo = $module->getSlotInfo($slotId);
$tokenInfo = $module->getTokenInfo($slotId);
All mechanisms declared in PKCS11 version 3 are available under the Pkcs11 namespace.
$mechanismList = $module->getMechanismList($slotId);
$mechanismInfo = $module->getMechanismInfo($slotId, Pkcs11\CKM_AES_GCM);
This extension supports initializing simple tokens via the C_InitToken
function.
$module->initToken($slotId, $label, $soPin);
You can open a session and login as either a Security Officer or user. From the returned Pkcs11\Session
object, more methods are available.
$session = $module->openSession($slotId, Pkcs11\CKF_RW_SESSION);
$session->login(Pkcs11\CKU_SO, $soPin);
$sessionInfo = $session->getInfo();
$session = $module->openSession($slotId, Pkcs11\CKF_RW_SESSION);
$session->login(Pkcs11\CKU_USER, $userPin);
$sessionInfo = $session->getInfo();
As a Security Officer, the user PIN can be set to any value.
$session->login(Pkcs11\CKU_SO, $soPin);
$session->initPin($userPin);
$session->logout();
As either user, the current user PIN can be changed.
$session->login(Pkcs11\CKU_SO, $soPin);
$session->setPin($soPin, $newPin);
$session->logout();
$session->login(Pkcs11\CKU_USER, $userPin);
$session->setPin($userPin, $newPin);
$session->logout();
You can generate symmetric keys for any available mechanism.
$key = $session->generateKey(new Pkcs11\Mechanism(Pkcs11\CKM_AES_KEY_GEN), [
Pkcs11\CKA_CLASS => Pkcs11\CKO_SECRET_KEY,
Pkcs11\CKA_SENSITIVE => true,
Pkcs11\CKA_ENCRYPT => true,
Pkcs11\CKA_DECRYPT => true,
Pkcs11\CKA_VALUE_LEN => 32,
Pkcs11\CKA_KEY_TYPE => Pkcs11\CKK_AES,
Pkcs11\CKA_LABEL => "Test AES",
Pkcs11\CKA_PRIVATE => true,
]);
$keypair = $session->generateKeyPair(new Pkcs11\Mechanism(Pkcs11\CKM_EC_KEY_PAIR_GEN), [
Pkcs11\CKA_VERIFY => true,
Pkcs11\CKA_LABEL => "Test ECDSA Public",
Pkcs11\CKA_EC_PARAMS => hex2bin('06082A8648CE3D030107'),
],[
Pkcs11\CKA_TOKEN => false,
Pkcs11\CKA_PRIVATE => true,
Pkcs11\CKA_SENSITIVE => true,
Pkcs11\CKA_SIGN => true,
Pkcs11\CKA_LABEL => "Test ECDSA Private",
]);
$pkey = $keypair->pkey;
$skey = $keypair->skey;
Given a symmetric key, you can encrypt something. Certain encryption mechanisms require a special Parameters object. Currently, the following are available:
- Pkcs11\GcmParams
- Pkcs11\Salsa20Params
- Pkcs11\ChaCha20Params
- Pkcs11\Salsa20Chacha20Poly1305Params
$iv = random_bytes(16);
$aad = '';
$tagLength = 128;
$gcmParams = new Pkcs11\GcmParams($iv, $aad, $tagLength);
$data = 'Hello World!';
$mechanism = new Pkcs11\Mechanism(Pkcs11\CKM_AES_GCM, $gcmParams);
$ciphertext = $key->encrypt($mechanism, $data);
var_dump(bin2hex($ciphertext));
// string(56) "67940e19213d68c88d163b12d6cd565300f70d693309b5b744085b35"
$plaintext = $key->decrypt($mechanism, $ciphertext);
var_dump($plaintext);
// string(12) "Hello World!"
AWS CloudHSM does things in a particular way. The initialization vector is generated by the HSM for you and returned prepended to the ciphertext.
$aad = '';
$tagLength = 128;
// No need to specify the $iv
$gcmParams = new Pkcs11\GcmParams('', $aad, $tagLength);
$data = 'Hello World!';
$mechanism = new Pkcs11\Mechanism(Pkcs11\CKM_CLOUDHSM_AES_GCM, $gcmParams);
$ciphertext = $key->encrypt($mechanism, $data);
var_dump(bin2hex($ciphertext));
// string(88) "0000000000000000000000000000000067940e19213d68c88d163b12d6cd565300f70d693309b5b744085b35"
$plaintext = $key->decrypt($mechanism, $ciphertext);
var_dump($plaintext);
// string(12) "Hello World!"
Given an RSA public key, you can encrypt a symmetric key. Similarly to symmetric mechanisms, some asymmetric encryption mechanisms require a special Parameters object. Currently, the following are available:
- Pkcs11\RsaOaepParams
$keypair = $session->generateKeyPair(new Pkcs11\Mechanism(Pkcs11\CKM_RSA_PKCS_KEY_PAIR_GEN), [
Pkcs11\CKA_ENCRYPT => true,
Pkcs11\CKA_MODULUS_BITS => 2048,
Pkcs11\CKA_PUBLIC_EXPONENT => hex2bin('010001'),
Pkcs11\CKA_LABEL => "Test RSA Encrypt Public",
],[
Pkcs11\CKA_TOKEN => false,
Pkcs11\CKA_PRIVATE => true,
Pkcs11\CKA_SENSITIVE => true,
Pkcs11\CKA_DECRYPT => true,
Pkcs11\CKA_LABEL => "Test RSA Encrypt Private",
]);
// SoftHSM2 only supports CKG_MGF1_SHA1
$oaepParam = new Pkcs11\RsaOaepParams(Pkcs11\CKM_SHA_1, Pkcs11\CKG_MGF1_SHA1);
$mechanism = new Pkcs11\Mechanism(Pkcs11\CKM_RSA_PKCS_OAEP, $oaepParam);
$symkey = random_bytes(32);
$ciphertext = $keypair->pkey->encrypt($mechanism, $symkey);
var_dump($ciphertext);
$plaintext = $keypair->skey->decrypt($mechanism, $ciphertext);
var_dump($plaintext);
Given an EC public key, you can derive a shared secret.
// P-384
$domainParameters = hex2bin('06052B81040022');
$rawPublickeyOther = hex2bin('049f0a09e8a6fc87f4804642c782b2cd3e566b3e62262090d94e12933a00916f559b62ea33197706a302f0722b781a9349ea8f0f2bcea854cdcf5d9ff0e0a19c3c35d63578292307d1d83031c0134700c2990ed5b38f6c92245103c2c1352132a3');
$keypair = $session->generateKeyPair(new Pkcs11\Mechanism(Pkcs11\CKM_EC_KEY_PAIR_GEN), [
Pkcs11\CKA_LABEL => "Test ECDH Public",
Pkcs11\CKA_EC_PARAMS => $domainParameters,
],[
Pkcs11\CKA_PRIVATE => true,
Pkcs11\CKA_SENSITIVE => true,
Pkcs11\CKA_DERIVE => true,
Pkcs11\CKA_LABEL => "Test ECDH Private",
]);
$shared = '';
// SoftHSM2 only supports CKD_NULL
$params = new Pkcs11\Ecdh1DeriveParams(Pkcs11\CKD_NULL, $shared, $rawPublickeyOther);
$mechanism = new Pkcs11\Mechanism(Pkcs11\CKM_ECDH1_DERIVE, $params);
$secret = $keypair->skey->derive($mechanism, [
Pkcs11\CKA_CLASS => Pkcs11\CKO_SECRET_KEY,
Pkcs11\CKA_KEY_TYPE => Pkcs11\CKK_AES,
Pkcs11\CKA_SENSITIVE => false,
Pkcs11\CKA_EXTRACTABLE => true,
Pkcs11\CKA_ENCRYPT => true,
Pkcs11\CKA_DECRYPT => true,
]);
$rawSecret = $secret->getAttributeValue([Pkcs11\CKA_VALUE])[Pkcs11\CKA_VALUE];
You have the ability to retrieve object using RFC7512 URIs.
Note: Currently, only the following path attributes are supported:
- id
- object
- type
$session->generateKeyPair(new Pkcs11\Mechanism(Pkcs11\CKM_RSA_PKCS_KEY_PAIR_GEN), [
Pkcs11\CKA_ENCRYPT => true,
Pkcs11\CKA_MODULUS_BITS => 2048,
Pkcs11\CKA_PUBLIC_EXPONENT => hex2bin('010001'),
Pkcs11\CKA_LABEL => "testPkcs11Url",
Pkcs11\CKA_ID => "testPkcs11UrlPublicId",
],[
Pkcs11\CKA_TOKEN => false,
Pkcs11\CKA_PRIVATE => true,
Pkcs11\CKA_SENSITIVE => true,
Pkcs11\CKA_DECRYPT => true,
Pkcs11\CKA_LABEL => "testPkcs11Url",
Pkcs11\CKA_ID => "testPkcs11UrlPrivateId",
]);
$privateKeySearchResult = $session->openUri("pkcs11:object=testPkcs11Url;type=private;");
$mechanism = new Pkcs11\Mechanism(Pkcs11\CKM_SHA256_RSA_PKCS);
$data = "Hello World!";
$signature = $privateKeySearchResult[0]->sign($mechanism, $data);
var_dump(bin2hex($signature));
$publicKeySearchResult = $session->openUri("pkcs11:id=testPkcs11UrlPublicId");
$valid = $publicKeySearchResult[0]->verify($mechanism, $data, $signature);
var_dump($valid);
Given an Object, you can retrieve it's readable attributes.
Note: the following attributes are not implemented and retrieving them throws an exception:
- CKA_WRAP_TEMPLATE
- CKA_UNWRAP_TEMPLATE
- CKA_DERIVE_TEMPLATE
Note: the following attributes internally provide a struct describing the date, but are here returned as a string:
- CKA_START_DATE
- CKA_END_DATE
$keypair = $session->generateKeyPair(new Pkcs11\Mechanism(Pkcs11\CKM_RSA_PKCS_KEY_PAIR_GEN), /* ... */);
$attributes = $keypair->skey->getAttributeValue([
Pkcs11\CKA_TOKEN,
Pkcs11\CKA_PRIVATE,
Pkcs11\CKA_SENSITIVE,
Pkcs11\CKA_EXTRACTABLE,
Pkcs11\CKA_NEVER_EXTRACTABLE,
Pkcs11\CKA_ALWAYS_SENSITIVE,
Pkcs11\CKA_SIGN,
Pkcs11\CKA_DECRYPT,
Pkcs11\CKA_PUBLIC_EXPONENT,
Pkcs11\CKA_LABEL,
]);
var_dump($attributes[Pkcs11\CKA_TOKEN]);
var_dump($attributes[Pkcs11\CKA_PRIVATE]);
var_dump($attributes[Pkcs11\CKA_SENSITIVE]);
var_dump($attributes[Pkcs11\CKA_EXTRACTABLE]);
var_dump($attributes[Pkcs11\CKA_NEVER_EXTRACTABLE]);
var_dump($attributes[Pkcs11\CKA_ALWAYS_SENSITIVE]);
var_dump($attributes[Pkcs11\CKA_SIGN]);
var_dump($attributes[Pkcs11\CKA_DECRYPT]);
var_dump(bin2hex($attributes[Pkcs11\CKA_PUBLIC_EXPONENT]));
var_dump($attributes[Pkcs11\CKA_LABEL]);
/* Outputs:
bool(false)
bool(true)
bool(true)
bool(false)
bool(true)
bool(true)
bool(true)
bool(false)
string(6) "010001"
string(16) "Test RSA Private"
*/