# OpenPGPpy
### OpenPGP smartcard communication library
A Python3 library to operate an OpenPGP device.
Provides access methods in Python to an OpenPGP card application, as defined in
https://gnupg.org/ftp/specs/OpenPGP-smart-card-application-3.3.pdf
No need to have GnuPG or similar binary, with OpenPGPpy one can setup and use an OpenPGP device (such as Yubico 5) right away in Python.
## Installation and requirements
Works with Python >= 3.6.
Requires PCSCd on Linux
Ubuntu / Debian
`(sudo) apt-get install python3-pip python3-pyscard python3-setuptools pcscd`
Fedora / CentOS / RHEL
`yum install python3-pip python3-pyscard python3-setuptools pcsc-lite-ccid`
On some Linux, starts PCSCd service
```
(sudo) systemctl start pcscd
(sudo) systemctl enable pcscd
```
It uses Pyscard, and this is listed in pip dependencies. So Pyscard is automatically installed when you install this package. In Linux, We recommend to install Pyscard using the distro package manager (see above).
### Installation of this library
Easiest way :
`python3 -m pip install OpenPGPpy`
From sources, download and run in this directory :
`python3 -m pip install .`
### Use
Instanciate a device with `OpenPGPpy.OpenPGPcard()`, then use methods functions of this object.
OpenPGPcard() throws an PGPCardException exception if no OpenPGP device can be found.
Basic example :
```
import OpenPGPpy
mydevice = OpenPGPpy.OpenPGPcard()
mydevice.verify_pin(1, "123456")
mydevice.sign(hash_or_msg_to_sign)
```
See demo and interface methods to get the full functions and details.
#### Demo
There are some demonstration scripts provided in the *demo* directory. They provide examples on how to use this library.
* reset.py : resets the OpenPGP device.
* decrypt.py : Generates an X25519 key pair that is used to DECipher data (compute X25519 ECDH).
* sign.py : Generates a 256k1 key pair, then uses it to sign data.
* signEd.py : Generates a Ed25519 key pair, then uses it to sign data.
The *decrypt.py* and *signEd.py* scripts require the pynacl library to check the device responses. This can be installed with the "dev" dependencies part of this package `python3 -m pip install .["dev"]` or just `python3 -m pip install pynacl`.
The *sign.py* script requires openssl binary in the user path to check the device responses.
*sign* and *signEd* can't be used together. Perform a reset of the card between one and the other.
Default PIN password for OpenPGP devices :
PIN1 : "123456"
PIN2 : "123456"
PIN3 : "12345678"
**Breaking changes** in v0.6 :
* The debug argument is removed from the instanciate method, and from the class object attribute. Now this library uses logger.debug(). Use a standard logger in your app and you'll get or print the debug traces from this library. It was previsouly printed out in the standard ouput, now it is always output in the logger debug.
* The get_application_data method returns a Python object, instead of the bytes array raw response from the device. The raw response is internally ASN1-BER decoded, with hex data strings for DOs content.
## Interface Methods
`OpenPGPcard( reader_index=None )`
Initializes the OpenPGP device object.
Connects to all readers seeking for an OpenPGP card, selects the app and loads its capabilities.
reader_index = None : the constructor will use the first OpenPGP card available in any readers slots detected.
reader_index = positive integer : will try to use the given reader/card at the given index (starts at 0).
The created object has the following attributes :
* .name : str, name of the device (or the card reader used)
* .pgpvermaj : int, OpenPGP application major version (2 or 3)
* .pgpvermin : int, OpenPGP application minor version
* .pgpverstr : string, OpenPGP application version "maj.min"
* .manufacturer_id : string, hex string of the manufacturer ID "0xXXXX"
* .manufacturer : string, name of the manufacturer (or "- unknown -")
* .serial : int, serial number
* .max_cmd : int, maximum command length
* .max_rsp : int, maximum response length
* .pw1_maxlen: int, maximum PIN1 length
* .pw3_maxlen: int, maximum PIN3 length
* .display : bool, has a display?
* .bio : bool, has a biometric sensor?
* .button : bool, has a button?
* .keypad : bool, has a keypad?
* .led : bool, has a LED?
* .speaker : bool, has a speaker?
* .mic : bool, has a microphone?
* .touchscreen : bool, has a touchscreen?
`OpenPGPcard.send_apdu( apduHeader, Data, ExpLongResp=0 )`
Sends full raw APDU, not supposed to be used by your scripts.
apduHeader and Data are lists of integers or bytesarray.
apduHeader is [ INS, CLA, P1, P2 ] ISO7816 APDU header, without length info (Lc nor Le).
Data is bytes list of the command data
ExpLongResp is optional integer, the expected response length. Required when sending a short command and receiving long data back.
In case data are cut in parts with "61" code, it automatically sends "C0" command to get remaining data and recontructs the full data.
Extended frame length is automatically managed.
*Note* if an extended data frame response is expected from a short command, the query command must be called with the ExpLongResp argument equals to 65536. Because the ISO7816 standard only allows symmetric type of communication (extended sent, extended received), and this is the easy way to force the command to be sent with an extended format.
Throws PGPCardException if answer status is not 0x9000.
Throws ConnectionException if the device is no more available.
Returns a bytearray of the card answer.
`OpenPGPcard.select_data( filehex, param_1=0, param_2=4 )`
Selects a data object ("DO").
filehex is 1 or 2 bytes object address in hex (2-4 string hex).
`OpenPGPcard.get_data( filehex )`
Reads a data object ("DO").
filehex is 1 or 2 bytes object address in hex (2-4 string hex).
Mostly used internally by others methods.
`OpenPGPcard.get_next_data( filehex, param_1=0, param_2=0 )`
Continue reading in data object ("DO").
filehex is 1 or 2 bytes object address in hex (2-4 string hex).
`OpenPGPcard.put_data( filehex, data_hex="" )`
Write data_hex in data object ("DO").
filehex is 1 or 2 bytes object address in hex (2-4 string hex).
Used in OpenPGP to configure the device, like key type or user info.
`OpenPGPcard.get_identifier()`
Reads and decode the Full Application Identifier (data object 0x4F).
Internally called at instanciation.
`OpenPGPcard.get_length()`
Only for OpenPGP v3. Reads and decode the Extended Length Info (data object "7F66").
Internally called at instanciation.
`OpenPGPcard.get_features()`
Reads and decode the optional General Feature Management (data object "7F74").
Internally called at instanciation. If not present, all features are supposed to be unavailable (attributes are False by default).
`OpenPGPcard.display_features()`
Prints the General Feature Management attributes.
Internally used by get_features for debug output.
`OpenPGPcard.get_historical_bytes()`
Raw read of the Historical Bytes (data object "5F52").
`OpenPGPcard.get_application_data()`
Get the Application Related Data (data object "6E").
Return the content of the "6E" data object as a Python object with hex encodings data.
`OpenPGPcard.get_pwstatus()`
Get the PW Status (data object "C4").
Return the content of the "C4" data object as raw bytearray data.
`OpenPGPcard.terminate_df()`
Send the TERMINATE DF command. Used to reset the card.
`OpenPGPcard.activate_file()`
Send the ACTIVATE FILE command. Used to reset the card.
`OpenPGPcard.reset( pin3 )`
Fully reset the device. Requires the "PUK" PIN #3 as a string.
`OpenPGPcard.get_random( data_length )`
Reads random data from the device, using the GET CHALLENGE command (data_length bytes long).
`OpenPGPcard.change_pin( old_pin, new_pin, pin_bank )`
Change the PIN in the bank 1 or 3 (PIN1 or PIN3). old_pin and new_pin are strings.
Minimum PIN length is 6 chars for PIN1, 8 chars for PIN3.
`OpenPGPcard.verify_pin( pin_bank, pin )`
Verify the PIN code : pin_bank is 1, 2 or 3 for respectively SW1, SW2 or SW3. pin is a string with the PIN.
`OpenPGPcard.get_pin_status( pin_bank )`
Reads PIN status : returns remaining tries left for the given PIN bank address (1, 2 or 3).
Return value is the number of remaining tries before the PIN block.
Return value is 0 : PIN is blocked (no more tries).
Return value is 9000 : PIN has been verified (OK).
`OpenPGPcard.gen_key( keypos_hex )`
Generates an assymetric key pair in a keypos slot address, by calling the GENERATE ASYMMETRIC KEY PAIR command. keypos_hex is the key object address (Control Reference Template) as 4 chars string (2 bytes address) : "B600" for sign key, "B800" for de/crypt key, "A400" for auth key.
Usually, the device reponds with the related public key of the key generated.
Requires the PIN3 "PUK" verified.
`OpenPGPcard.get_public_key( keypos_hex )`
Reads the public key in keypos slot address, by calling the GENERATE ASYMMETRIC KEY PAIR command (with the read pubkey flag). keypos_hex is the key object address (Control Reference Template) as 4 chars string "hex" (2 bytes address) : "B600" for sign key ref1, "B800" for de/crypt key ref2, "A400" for auth key ref3.
Requires the related PIN verified.
`OpenPGPcard.sign( data )`
Signs data with the internal device SIGn key (Ref1), calling the COMPUTE DIGITAL SIGNATURE command.
data is in bytes "binary" format. With ECDSA, data is the hash to sign.
Requires the PIN1 verified.
See the OpenPGP application standard for more details about data format.
`OpenPGPcard.sign_ec_der( datahash )`
ECDSA signs with the internal device SIGn key the hash datahash (bytes) and outputs the signature as ASN1 DER encoded (bytes). Requires the SIG key to be an EC type key pair ("13..." in "C1").
Requires the PIN1 verified.
`OpenPGPcard.decipher( data )`
Decrypts data with the internal device DECryption key (Ref2), calling the DECIPHER command.
data is in bytes "binary" format.
For RSA : decrypts data, data input must be formatted according to PKCS#1 before encryption (device is checking padding conformance).
For EC : performs an ECDH with the provided public key in data and the internal device DECryption private key.
Requires the PIN2 verified.
See the OpenPGP application standard for more details.
`OpenPGPcard.decipher_25519( external_publickey )`
Decrypts data with the internal device DECryption key with X25519 (Curve25519 ECDH). Obviously, requires the DEC key to be a Curve25519 key pair ("122B060104019755010501" in "C2"). As DECIPHER with EC, the device doesn\'t decrypt data, but computes the private "shared" symmetric key with ECDH. Still quite like RSA where the decrypted data is also the private shared symmetric key.
external_publickey argument is "x" 32 bytes, as bytes. It performs an ECDH with the provided public key and the internal device DECryption private key.
Requires the PIN2 verified.
## License
Copyright (C) 2020-2024 BitLogiK SAS
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, version 3 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
## ToDo
* Secure Messaging
* Verify
* Sign helpers (RSA Tag/DSI)
* Encipher
* Make it more user friendly with more abstraction layers and data list, for example set_key(2, "X25519") sends PUT_DATA("122B060104019755010501") in "C2"
## Support
Open an issue in the Github repository for help about its use.
Raw data
{
"_id": null,
"home_page": "https://github.com/bitlogik/OpenPGPpy",
"name": "OpenPGPpy",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.6",
"maintainer_email": null,
"keywords": "cryptography security openpgp hardware",
"author": "BitLogiK",
"author_email": "contact@bitlogik.fr",
"download_url": null,
"platform": null,
"description": "# OpenPGPpy\r\n\r\n### OpenPGP smartcard communication library\r\n\r\nA Python3 library to operate an OpenPGP device.\r\n\r\nProvides access methods in Python to an OpenPGP card application, as defined in \r\nhttps://gnupg.org/ftp/specs/OpenPGP-smart-card-application-3.3.pdf\r\n\r\nNo need to have GnuPG or similar binary, with OpenPGPpy one can setup and use an OpenPGP device (such as Yubico 5) right away in Python.\r\n\r\n## Installation and requirements\r\n\r\nWorks with Python >= 3.6.\r\n\r\nRequires PCSCd on Linux\r\n\r\nUbuntu / Debian \r\n`(sudo) apt-get install python3-pip python3-pyscard python3-setuptools pcscd`\r\n\r\nFedora / CentOS / RHEL \r\n`yum install python3-pip python3-pyscard python3-setuptools pcsc-lite-ccid`\r\n\r\nOn some Linux, starts PCSCd service\r\n\r\n```\r\n(sudo) systemctl start pcscd\r\n(sudo) systemctl enable pcscd\r\n```\r\n\r\nIt uses Pyscard, and this is listed in pip dependencies. So Pyscard is automatically installed when you install this package. In Linux, We recommend to install Pyscard using the distro package manager (see above).\r\n\r\n### Installation of this library\r\n\r\nEasiest way : \r\n`python3 -m pip install OpenPGPpy` \r\n\r\nFrom sources, download and run in this directory : \r\n`python3 -m pip install .`\r\n\r\n### Use\r\n\r\nInstanciate a device with `OpenPGPpy.OpenPGPcard()`, then use methods functions of this object.\r\n\r\nOpenPGPcard() throws an PGPCardException exception if no OpenPGP device can be found.\r\n\r\nBasic example :\r\n\r\n```\r\nimport OpenPGPpy\r\nmydevice = OpenPGPpy.OpenPGPcard()\r\nmydevice.verify_pin(1, \"123456\")\r\nmydevice.sign(hash_or_msg_to_sign)\r\n```\r\n\r\nSee demo and interface methods to get the full functions and details.\r\n\r\n#### Demo\r\n\r\nThere are some demonstration scripts provided in the *demo* directory. They provide examples on how to use this library.\r\n\r\n* reset.py : resets the OpenPGP device.\r\n* decrypt.py : Generates an X25519 key pair that is used to DECipher data (compute X25519 ECDH).\r\n* sign.py : Generates a 256k1 key pair, then uses it to sign data.\r\n* signEd.py : Generates a Ed25519 key pair, then uses it to sign data.\r\n\r\nThe *decrypt.py* and *signEd.py* scripts require the pynacl library to check the device responses. This can be installed with the \"dev\" dependencies part of this package `python3 -m pip install .[\"dev\"]` or just `python3 -m pip install pynacl`.\r\n\r\nThe *sign.py* script requires openssl binary in the user path to check the device responses.\r\n\r\n*sign* and *signEd* can't be used together. Perform a reset of the card between one and the other.\r\n\r\nDefault PIN password for OpenPGP devices : \r\nPIN1 : \"123456\" \r\nPIN2 : \"123456\" \r\nPIN3 : \"12345678\"\r\n\r\n**Breaking changes** in v0.6 :\r\n\r\n* The debug argument is removed from the instanciate method, and from the class object attribute. Now this library uses logger.debug(). Use a standard logger in your app and you'll get or print the debug traces from this library. It was previsouly printed out in the standard ouput, now it is always output in the logger debug.\r\n* The get_application_data method returns a Python object, instead of the bytes array raw response from the device. The raw response is internally ASN1-BER decoded, with hex data strings for DOs content.\r\n\r\n## Interface Methods\r\n\r\n`OpenPGPcard( reader_index=None )` \r\nInitializes the OpenPGP device object. \r\nConnects to all readers seeking for an OpenPGP card, selects the app and loads its capabilities. \r\nreader_index = None : the constructor will use the first OpenPGP card available in any readers slots detected. \r\nreader_index = positive integer : will try to use the given reader/card at the given index (starts at 0).\r\n\r\nThe created object has the following attributes :\r\n\r\n* .name : str, name of the device (or the card reader used)\r\n* .pgpvermaj : int, OpenPGP application major version (2 or 3)\r\n* .pgpvermin : int, OpenPGP application minor version\r\n* .pgpverstr : string, OpenPGP application version \"maj.min\"\r\n* .manufacturer_id : string, hex string of the manufacturer ID \"0xXXXX\"\r\n* .manufacturer : string, name of the manufacturer (or \"- unknown -\")\r\n* .serial : int, serial number\r\n* .max_cmd : int, maximum command length\r\n* .max_rsp : int, maximum response length\r\n* .pw1_maxlen: int, maximum PIN1 length\r\n* .pw3_maxlen: int, maximum PIN3 length\r\n* .display : bool, has a display?\r\n* .bio : bool, has a biometric sensor?\r\n* .button : bool, has a button?\r\n* .keypad : bool, has a keypad?\r\n* .led : bool, has a LED?\r\n* .speaker : bool, has a speaker?\r\n* .mic : bool, has a microphone?\r\n* .touchscreen : bool, has a touchscreen?\r\n\r\n`OpenPGPcard.send_apdu( apduHeader, Data, ExpLongResp=0 )` \r\nSends full raw APDU, not supposed to be used by your scripts. \r\napduHeader and Data are lists of integers or bytesarray. \r\napduHeader is [ INS, CLA, P1, P2 ] ISO7816 APDU header, without length info (Lc nor Le). \r\nData is bytes list of the command data \r\nExpLongResp is optional integer, the expected response length. Required when sending a short command and receiving long data back. \r\nIn case data are cut in parts with \"61\" code, it automatically sends \"C0\" command to get remaining data and recontructs the full data. \r\nExtended frame length is automatically managed. \r\n*Note* if an extended data frame response is expected from a short command, the query command must be called with the ExpLongResp argument equals to 65536. Because the ISO7816 standard only allows symmetric type of communication (extended sent, extended received), and this is the easy way to force the command to be sent with an extended format. \r\nThrows PGPCardException if answer status is not 0x9000. \r\nThrows ConnectionException if the device is no more available. \r\nReturns a bytearray of the card answer.\r\n\r\n`OpenPGPcard.select_data( filehex, param_1=0, param_2=4 )` \r\nSelects a data object (\"DO\"). \r\nfilehex is 1 or 2 bytes object address in hex (2-4 string hex).\r\n\r\n`OpenPGPcard.get_data( filehex )` \r\nReads a data object (\"DO\"). \r\nfilehex is 1 or 2 bytes object address in hex (2-4 string hex). \r\nMostly used internally by others methods.\r\n\r\n`OpenPGPcard.get_next_data( filehex, param_1=0, param_2=0 )` \r\nContinue reading in data object (\"DO\"). \r\nfilehex is 1 or 2 bytes object address in hex (2-4 string hex). \r\n\r\n`OpenPGPcard.put_data( filehex, data_hex=\"\" )` \r\nWrite data_hex in data object (\"DO\"). \r\nfilehex is 1 or 2 bytes object address in hex (2-4 string hex). \r\nUsed in OpenPGP to configure the device, like key type or user info.\r\n\r\n`OpenPGPcard.get_identifier()` \r\nReads and decode the Full Application Identifier (data object 0x4F). \r\nInternally called at instanciation.\r\n\r\n`OpenPGPcard.get_length()` \r\nOnly for OpenPGP v3. Reads and decode the Extended Length Info (data object \"7F66\"). \r\nInternally called at instanciation.\r\n\r\n`OpenPGPcard.get_features()` \r\nReads and decode the optional General Feature Management (data object \"7F74\"). \r\nInternally called at instanciation. If not present, all features are supposed to be unavailable (attributes are False by default).\r\n\r\n`OpenPGPcard.display_features()` \r\nPrints the General Feature Management attributes.\r\nInternally used by get_features for debug output.\r\n\r\n`OpenPGPcard.get_historical_bytes()` \r\nRaw read of the Historical Bytes (data object \"5F52\").\r\n\r\n`OpenPGPcard.get_application_data()` \r\nGet the Application Related Data (data object \"6E\"). \r\nReturn the content of the \"6E\" data object as a Python object with hex encodings data.\r\n\r\n`OpenPGPcard.get_pwstatus()` \r\nGet the PW Status (data object \"C4\"). \r\nReturn the content of the \"C4\" data object as raw bytearray data.\r\n\r\n`OpenPGPcard.terminate_df()` \r\nSend the TERMINATE DF command. Used to reset the card.\r\n\r\n`OpenPGPcard.activate_file()` \r\nSend the ACTIVATE FILE command. Used to reset the card.\r\n\r\n`OpenPGPcard.reset( pin3 )` \r\nFully reset the device. Requires the \"PUK\" PIN #3 as a string.\r\n\r\n`OpenPGPcard.get_random( data_length )` \r\nReads random data from the device, using the GET CHALLENGE command (data_length bytes long).\r\n\r\n`OpenPGPcard.change_pin( old_pin, new_pin, pin_bank )` \r\nChange the PIN in the bank 1 or 3 (PIN1 or PIN3). old_pin and new_pin are strings. \r\nMinimum PIN length is 6 chars for PIN1, 8 chars for PIN3.\r\n\r\n`OpenPGPcard.verify_pin( pin_bank, pin )` \r\nVerify the PIN code : pin_bank is 1, 2 or 3 for respectively SW1, SW2 or SW3. pin is a string with the PIN.\r\n\r\n`OpenPGPcard.get_pin_status( pin_bank )` \r\nReads PIN status : returns remaining tries left for the given PIN bank address (1, 2 or 3). \r\nReturn value is the number of remaining tries before the PIN block. \r\nReturn value is 0 : PIN is blocked (no more tries). \r\nReturn value is 9000 : PIN has been verified (OK).\r\n\r\n`OpenPGPcard.gen_key( keypos_hex )` \r\nGenerates an assymetric key pair in a keypos slot address, by calling the GENERATE ASYMMETRIC KEY PAIR command. keypos_hex is the key object address (Control Reference Template) as 4 chars string (2 bytes address) : \"B600\" for sign key, \"B800\" for de/crypt key, \"A400\" for auth key. \r\nUsually, the device reponds with the related public key of the key generated. \r\nRequires the PIN3 \"PUK\" verified.\r\n\r\n`OpenPGPcard.get_public_key( keypos_hex )` \r\nReads the public key in keypos slot address, by calling the GENERATE ASYMMETRIC KEY PAIR command (with the read pubkey flag). keypos_hex is the key object address (Control Reference Template) as 4 chars string \"hex\" (2 bytes address) : \"B600\" for sign key ref1, \"B800\" for de/crypt key ref2, \"A400\" for auth key ref3. \r\nRequires the related PIN verified.\r\n\r\n`OpenPGPcard.sign( data )` \r\nSigns data with the internal device SIGn key (Ref1), calling the COMPUTE DIGITAL SIGNATURE command. \r\ndata is in bytes \"binary\" format. With ECDSA, data is the hash to sign. \r\nRequires the PIN1 verified. \r\nSee the OpenPGP application standard for more details about data format.\r\n\r\n`OpenPGPcard.sign_ec_der( datahash )`\r\nECDSA signs with the internal device SIGn key the hash datahash (bytes) and outputs the signature as ASN1 DER encoded (bytes). Requires the SIG key to be an EC type key pair (\"13...\" in \"C1\"). \r\nRequires the PIN1 verified.\r\n\r\n`OpenPGPcard.decipher( data )` \r\nDecrypts data with the internal device DECryption key (Ref2), calling the DECIPHER command. \r\ndata is in bytes \"binary\" format. \r\nFor RSA : decrypts data, data input must be formatted according to PKCS#1 before encryption (device is checking padding conformance). \r\nFor EC : performs an ECDH with the provided public key in data and the internal device DECryption private key. \r\nRequires the PIN2 verified. \r\nSee the OpenPGP application standard for more details.\r\n\r\n`OpenPGPcard.decipher_25519( external_publickey )` \r\nDecrypts data with the internal device DECryption key with X25519 (Curve25519 ECDH). Obviously, requires the DEC key to be a Curve25519 key pair (\"122B060104019755010501\" in \"C2\"). As DECIPHER with EC, the device doesn\\'t decrypt data, but computes the private \"shared\" symmetric key with ECDH. Still quite like RSA where the decrypted data is also the private shared symmetric key. \r\nexternal_publickey argument is \"x\" 32 bytes, as bytes. It performs an ECDH with the provided public key and the internal device DECryption private key. \r\nRequires the PIN2 verified. \r\n\r\n## License\r\n\r\nCopyright (C) 2020-2024 BitLogiK SAS\r\n\r\nThis program is free software: you can redistribute it and/or modify \r\nit under the terms of the GNU General Public License as published by \r\nthe Free Software Foundation, version 3 of the License.\r\n\r\nThis program is distributed in the hope that it will be useful, \r\nbut WITHOUT ANY WARRANTY; without even the implied warranty of \r\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. \r\nSee the GNU General Public License for more details.\r\n\r\n## ToDo\r\n\r\n* Secure Messaging\r\n* Verify\r\n* Sign helpers (RSA Tag/DSI)\r\n* Encipher\r\n* Make it more user friendly with more abstraction layers and data list, for example set_key(2, \"X25519\") sends PUT_DATA(\"122B060104019755010501\") in \"C2\"\r\n\r\n## Support\r\n\r\nOpen an issue in the Github repository for help about its use.\r\n\r\n\r\n",
"bugtrack_url": null,
"license": "GPLv3",
"summary": "OpenPGP smartcard communication library",
"version": "1.2",
"project_urls": {
"Homepage": "https://github.com/bitlogik/OpenPGPpy"
},
"split_keywords": [
"cryptography",
"security",
"openpgp",
"hardware"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "4cda16ef84eea88ba4c16c53dc93759ba5c649b021cbbfa42154897bc0257160",
"md5": "7afd5645500d574f54f80665d512b384",
"sha256": "c4966f8df340246232c61fe04d9dcdd87c7fc07f942afa4cc974f546bce3d790"
},
"downloads": -1,
"filename": "OpenPGPpy-1.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "7afd5645500d574f54f80665d512b384",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.6",
"size": 24908,
"upload_time": "2024-06-02T22:11:58",
"upload_time_iso_8601": "2024-06-02T22:11:58.031404Z",
"url": "https://files.pythonhosted.org/packages/4c/da/16ef84eea88ba4c16c53dc93759ba5c649b021cbbfa42154897bc0257160/OpenPGPpy-1.2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-06-02 22:11:58",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "bitlogik",
"github_project": "OpenPGPpy",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "openpgppy"
}