Python 3 tutorial
Prerequisites
-
Linux on x86_64
-
Windows on x86_64
-
Security World 13.6 or later.
-
A user permitted to connect to the local hardserver and read the Security World key management data (kmdata) files.
-
Python 3.11
Set up the environment for nfpython
To use nShield Python 3 support with another version of Python 3, contact Entrust Support. Other Python versions are not covered by this guide. |
The recommended way of developing and deploying an nShield Python 3 application is using the Python virtualenv created from the nShield Python 3 containing the packages your project requires.
Before you begin, install the optional "CipherTools" (Windows) or "ctd" (Linux) component as this contains the Python wheel files you require.
Entrust recommends the following directory layout:
application venv mypackage __init__.py code.py tests test_mypackage.py script.py setup.py
Create and configure the virtualenv
You do not need administrator access to create a virtualenv. When launching production applications, you must use the virtualenv that was created for them.
-
Start a command-line shell.
-
Change to the directory where you want to store your application files.
-
Run:
Linux/opt/nfast/python3/bin/python3 -m venv venv
Windowsc:\Program Files\nCipher\nfast\python3\python --copies -m venv venv
-
Install all required Python packages into the virtualenv, for example:
Linux. venv/bin/activate pip install /opt/nfast/python3/additional-packages/nfpython*.whl
Windows (PowerShell)venv\Scripts\activate.ps1 pip install c:\Program Files\nCipher\nfast\python3\additional-packages\nfpython*.whl
Entrust recommendeds that you use a requirements.txt
file orsetup.py
/setup.cfg
to define your dependencies, including the nfpython package.
To run your application:
-
If your application uses
setup.py
entrypoint scripts, execute them directly. -
If you do not use entrypoints, you will need create your own scripts:
These examples assume that your program is called application.py
and your virtualenv is in thevenv
directory.Linux - application.sh#!/bin/sh HERE=$(dirname $(readlink -f $0)) . ${HERE}/venv/bin/activate python3 ${HERE}/application.py
Windows - application.ps1$PSScriptRoot\venv\Scripts\activate.ps1 python $PSScriptRoot\application.py
nfpython connections and commands
Send nCore commands to the hardserver or attached HSMs:
-
Import the nShield python module.
import nfpython
-
Set up a connection
conn = nfpython.connection()
-
Construct an nCore command:
c = nfpython.Command() c.cmd = "NewEnquiry" c.args.module = 1 c.args.version = 1
The easiest way to define a new command to send is to create a
Command()
object, set the attributes starting with thecmd
name, then set theargs
attributes as required. -
Send the command, wait for the reply, and then print it:
reply = conn.transact(c) print(reply.reply.data.one)
This command prints a reply similar to the following output.
EnquiryDataOne.releasemajor= 13 .releaseminor= 6 .releasepatch= 5 .checkintimehigh= 0 .checkintimelow= 1620223158 .flags= Hardware|HasTokens|SupportsCommandState .speedindex= 14200 .recommendedminq= 9 .recommendedmaxq= 152 .hardwareserial= ABCD-8287-C172 .softwaredetails= 13.6.5-120-a44e176921
If the command results in a reply with a status value other than OK
, the transact()
connection method raises an NFStatusError
exception.
To suppress the exception and obtain the original reply regardless of status, use the ignorestatus=True
keyword argument:
import nfpython
conn = nfpython.connection()
c = nfpython.Command()
c.cmd = "NewEnquiry"
c.args.module = 1000
reply = conn.transact(c, ignorestatus=True)
print(reply)
This prints:
Reply.cmd= ErrorReturn
.status= InvalidModule
.flags= 0x0
Worked nfpython example for hash, sign, and verify
nShield Security World software includes several Python 3 example files.
The default location for these files is:
-
Linux:
/opt/nfast/python3/examples
-
Windows:
C:\Program Files\nCipher\nfast\python3\examples
The files hashing.py
, keys.py
and signing.py
make up a sample application that signs data using a previously generated RSA key.
The application performs the following steps: load keys, digest data, and perform a sign and verify operation using an attached HSM.
-
Generate a module-protected RSA key:
Linux/opt/nfast/bin/generatekey -b simple protect=module type=RSA size=2048 ident=signer
Windowsc:\Program Files\nCipher\nfast\bin\generatekey -b simple protect=module type=RSA size=2048 ident=signer
-
Find and load the keys
Finding saved keys requires the
nfpython
andnfkm
modules.To load an existing key, you need to know the
appname
andident
of the saved key, and you need thenfpython
connection.import nfpython import nfkm appname = "simple" ident = "signer" module = 1 conn = nfpython.connection(needworldinfo=True) appident = nfkm.KeyIdent(appname=appname, ident=ident) keydata = nfkm.findkey(conn, appident) # load the private key from keydata.privblob # or public key from keydata.pubblob cmd = nfpython.Command(cmd="LoadBlob") cmd.args.blob = keydata.privblob cmd.args.module = module # load the blob and get a Key ID rep = conn.transact(cmd) keyid = rep.reply.idka
-
Process the data.
For large amounts of data, nShield software provides channels to perform crypto operations in an incremental stream.
This example uses the
ChannelOpen
andChannelUpdate
commands to compute a SHA256 hash with the HSM.Entrust typically recommends a
ChannelUpdate
size of around 8000 bytes. However, you might find that larger or smaller sizes give better results depending on network conditions or HSM speed ratings.message = b"hello world" * 10240 chunksize = 8000 conn = nfpython.connection(needworldinfo=True) c = nfpython.Command() c.cmd = "ChannelOpen" c.args.type = "simple" c.args.mode = "sign" c.args.mech = "SHA256Hash" rep = conn.transact(c) channel = rep.reply.idch c = nfpython.Command() c.cmd = "ChannelUpdate" # split the message up into small chunks and transmit each in sequence for chunk in (message[i:i+chunksize] for i in range(0, len(message), chunksize)): c.args.idch = channel c.args.input = nfpython.ByteBlock(chunk, fromraw=True) conn.transact(c) # obtain the hash value by setting the final flag c.args.input = nfpython.ByteBlock() c.args.flags |= "final" rep = conn.transact(c) digest = rep.reply.output
-
Sign the digest using the loaded private key:
# compute the hash (either using nfpython or hashlib) digest = digest_message(conn, message) # perform a signature plain = nfpython.Hash32(digest) c = nfpython.Command() c.cmd = "Sign" c.args.mech = "RSAhSHA256pPKCS1" c.args.key = privkey c.args.plain.type = "Hash32" c.args.plain.data.data = plain rep = conn.transact(c) signature = rep.reply.sig
-
Verify the signature.
Verification requires a public key, the signature plaintext, and the signature data.
If the signature is invalid, transact(cmd)
raises anNFStatusError
exception with its status attribute set to"VerifyFailed"
.digest = nfpython.Hash32(hashbytes) c = nfpython.Command() c.cmd = "Verify" c.args.key = pubkey c.args.plain.type = "Hash32" c.args.plain.data.data = digest c.args.sig = signature conn.transact(c)
The full example would use the following code:
import nfpython
import nfkm
def load_key(conn, appname: str, ident: str, module=0, private=True) -> nfpython.KeyID:
"""
Load a key given an appname and ident
:param conn:
:param appname: Key appname, eg "simple"
:param ident: Key ident
:param module: module to load the key on (default 0 = any)
:param private: load the private blob if true
:return: the loaded key
"""
appident = nfkm.KeyIdent(appname=appname, ident=ident)
keydata = nfkm.findkey(conn, appident)
cmd = nfpython.Command(cmd="LoadBlob")
if private:
cmd.args.blob = keydata.privblob
else:
cmd.args.blob = keydata.pubblob
cmd.args.module = module
rep = conn.transact(cmd)
keyid = rep.reply.idka
return keyid
import nfpython
def digest_message(conn, message: bytes, module=0, mech="Sha256Hash", chunksize=8000) -> bytes:
"""
Hash a binary string using the HSM and ChannelUpdate commands
:param conn:
:param message: binary message to hash.
:param module: HSM to sign with (default 0 = any)
:param mech: hash mechanism name
:param chunksize: digest block size
:return:
"""
c = nfpython.Command()
c.cmd = "ChannelOpen"
c.args.type = "simple"
c.args.mode = "sign"
c.args.mech = mech
c.args.module = module
rep = conn.transact(c)
channel = rep.reply.idch
c = nfpython.Command()
c.cmd = "ChannelUpdate"
# split the message up into small chunks and transmit each in sequence
for chunk in (message[i:i+chunksize] for i in range(0, len(message), chunksize)):
c.args.idch = channel
c.args.input = nfpython.ByteBlock(chunk, fromraw=True)
conn.transact(c)
# obtain the hash value by setting the final flag
c.args.input = nfpython.ByteBlock()
c.args.flags |= "final"
rep = conn.transact(c)
digest = rep.reply.output
return digest
#!/usr/bin/env python3
import nfpython
from keys import load_key
from hashing import digest_message
def sign_message(conn, privkey: nfpython.KeyID, message: bytes) -> (nfpython.Hash32, nfpython.CipherText):
"""
Hash and sign a binary string using the HSM and a loaded RSA private key
:param conn:
:param privkey: KeyID of loaded key
:param message: bytes to sign
:return: the digest hash and signature
"""
digest = digest_message(conn, message)
plain = nfpython.Hash32(digest)
c = nfpython.Command()
c.cmd = "Sign"
c.args.mech = "RSAhSHA256pPKCS1"
c.args.key = privkey
c.args.plain.type = "Hash32"
c.args.plain.data.data = plain
rep = conn.transact(c)
signature = rep.reply.sig
return plain, signature
def verify_signature(conn, pubkey: nfpython.KeyID, digest, signature) -> bool:
"""
Verify a signature using the HSM and a loaded public key
:param conn:
:param pubkey:
:param digest:
:param signature:
:return:
"""
digest[0] = 1
cmd = nfpython.Command()
cmd.cmd = "Verify"
cmd.args.key = pubkey
cmd.args.plain.type = "Hash32"
cmd.args.plain.data.data = digest
cmd.args.sig = signature
conn.transact(cmd)
def run():
conn = nfpython.connection(needworldinfo=True)
privkey = load_key(conn, appname="simple", ident="signer")
pubkey = load_key(conn, appname="simple", ident="signer", private=False)
message_bytes = b"hello world" * 1024
print(f"Hash and Sign {len(message_bytes)} bytes..")
digest, signature = sign_message(conn, privkey, message_bytes)
print("Verifying..")
verify_signature(conn, pubkey, digest, signature)
print("Done.")
if __name__ == "__main__":
run()