#!/usr/bin/python3 """ * File : pcp2 [The Pure Crypto Project https://senderek.ie/pcp] * Version : 2.0 * Date : 6 Jan 2025 * License : BSD * * Copyright (c) 2003 - 2025 * Ralf Senderek, Ireland. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Ralf Senderek. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * """ import sys import time import os # default intallation path for pcp modules sys.path.append("/usr/share/pcp2") import pure EOL = "\n" Text = [] ERR_USE = 1 ERR_PERM = 2 ERR_PASSWORD = 3 ERR_CORRUPT = 4 ERR_WRONGKEY = 5 ERR_SIG = 7 ERR_CODE = 8 Hashtype = "Hash: SDLH " Comment = "*** based on modular exponentiation and RSA alone ***" #-----------------------------------------------------------# # Functions # #-----------------------------------------------------------# def ENCRYPTION() : # form a single bytes object from list of bytes ( or Text ) Plaintext = b'' for Line in Text : Plaintext = Plaintext + Line # select a recipient's cryptosystem (signed public key) UserInformation = "l" Prompt = EOL + "To select the recipient's public key, please enter the recipient's user ID (or list item) :"+EOL+ " shows a list."+EOL while UserInformation == "l" or UserInformation == "L" or len(UserInformation) == 0 : UserInformation = input(Prompt) if UserInformation == "l" or UserInformation == "L": pure.print_pubkeylist() # select only encryption keys Result = pure.select_cryptosystem(UserInformation, True) if Result : print ("Encrypting with public key for " , pure.UserID.decode(), EOL) Crypt = pure.encrypt(Plaintext, pure.Encryption, pure.Modulus) Ciphertext = "" for Number in Crypt : Ciphertext = Ciphertext + str(Number) + EOL try: FILE = open(sys.argv[2]+".pcp" , "w") FILE.write(Ciphertext) FILE.close() print (EOL + "Cryptogram written to file "+ sys.argv[2]+".pcp" + EOL) except: print (EOL + "Error: Cannot write file ", sys.argv[2]+".pcp" + EOL) sys.exit(ERR_PERM) else: print (EOL + "Encryption error: No public key found for " + UserInformation + EOL) sys.exit(ERR_WRONGKEY) #-----------------------------------------------------------# def DECRYPTION() : Cryptogram = [] Errors = 0 Plain = b'' for Line in Text : try: Number = pure.toLong(Line) Cryptogram.append(Number) except: Errors = Errors + 1 if Errors : print (Errors, " lines of your ciphertext are not PCP encrypted.") sys.exit(ERR_CORRUPT) if Errors == len(Text) : print ("This is not a PCP encrypted file") sys.exit(ERR_CORRUPT) pure.read_cryptosystem("encryptionkey", True) print ("Encryption key is used.") pure.print_securityhash(True) pure.load_privatekey() Plain = pure.decrypt(Cryptogram, pure.Decryption, pure.Modulus) pure.burn_privatekey() try : FILE = open(sys.argv[2]+".clear", "wb") FILE.write(Plain) FILE.close() print (EOL + "Plaintext written to file "+ sys.argv[2]+".clear" + EOL) if pure.OS == "unix": os.system("chmod 600 " + sys.argv[2] + ".clear") except: print ("Error: Cannot write plaintext file.") sys.exit(ERR_PERM) #-----------------------------------------------------------# def SIGNING(SigType) : print (EOL + "Signing ..." + EOL) # check if Signstring contains crypto Crypto = 0 for Line in Text : try: N = long(Line) Crypto = Crypto + 1 except: pass if Crypto : print (EOL + "WARNING Your message contains encrypted material. WARNING") print ("This is dangerous! Always sign before encrypting.") print ("But anyway, if you wish to continue." + EOL) Signstring = b'' for Line in Text: Signstring = Signstring + Line pure.read_cryptosystem("signingkey", True) Fingerprint = pure.hash(Signstring) Timestamp = time.ctime(time.time())[:32] print ("hash = " , str(Fingerprint)) print ("time = " , Timestamp) print ("UID = " , pure.toString(pure.UserID)) Infostring = Timestamp # make sure the Infostring is not too large if len(Infostring) > (pure.ModulusMargin - 256)//8 : Infostring = Infostring[:(pure.ModulusMargin - 256)//8] print ("The infostring has been cropped.") Info = 0 for Character in Infostring : Info = Info * 256 + ord(Character) # a signature number to be modexp()ed has the following structure # and is less than the signing key's modulus with a 8 bit margin: # # Timestamp | Nonce | Fingerprint XOR hash(Nonce) # # Fingerprint XOR hash(Nonce) is HashmodulusLength bits long # Nonce is 256 bits long # Timestamp is 256 bits long # # The signing key's modulus must be at least 256+256+8 = 520 bits larger # than the hashkey modulus # construct the data to be signed Exp1 = pure.HashModulusLength Exp2 = pure.HashModulusLength+256 Nonce = pure.getNonce() # blind the random nonce with the securityhash that represents the signing key BlindNonce = Nonce ^ pure.Securityhash SignatureNumber = 0 SignatureNumber = Info * pow(2,Exp2) + BlindNonce* pow(2,Exp1) + Fingerprint ^ pure.hash(pure.toBytes(Nonce)) if SignatureNumber * 256 >= pure.Modulus : print ("WARNING: The Signature-Information is too long!") print ("No Signature created.") InfoBits = pure.countbits(Info) print ("The Signature-Information is ", InfoBits , " bits long.") print ("Use a longer signing key.") sys.exit(ERR_SIG) # generate the signature pure.load_privatekey() Signature = 0 Signature = pure.ModExp(SignatureNumber, pure.Decryption, pure.Modulus) pure.burn_privatekey() # check if signature verifies with the signer's public key Challenge = 0 Challenge = pure.ModExp(Signature, pure.Encryption, pure.Modulus) if Challenge != SignatureNumber : print ("Error: No signature created."+EOL) sys.exit(ERR_CORRUPT) SignatureText = "" if SigType == "clear": Contents = pure.toString(Signstring) if Contents: SignatureText = SignatureText + "-----BEGIN PURE-CRYPTO SIGNED MESSAGE-----"+EOL SignatureText = SignatureText + Contents + EOL else: print("You cannot clearsign binary data. Please use \"-ds\" for this purpose.") sys.exit( ERR_SIG ) SignatureText = SignatureText + "-----BEGIN PURE-CRYPTO SIGNATURE-----"+EOL SignatureText = SignatureText + Hashtype + Comment + EOL + EOL SignatureText = SignatureText + pure.toString(pure.UserID) + EOL SignatureText = SignatureText + str(Signature) + EOL SignatureText = SignatureText + "-----END PURE-CRYPTO SIGNATURE-----" + EOL if sys.argv[2] == "-pipe": OUTFILE.write(SignatureText) OUTFILE.close() else: try: FILE = open(sys.argv[2]+".sig", "w") FILE.write(SignatureText) FILE.close() print (EOL + "Signature written to file ", sys.argv[2] + ".sig" + EOL) except: print ("Error: Cannot write file ", sys.argv[2]+".sig" + EOL) sys.exit(ERR_PERM) #-----------------------------------------------------------# def VERIFICATION() : print() print("Verifying signature on file " + sys.argv[2]) MessageText = [] preface = b'' EncryptedSignature = 0 epilogue = b'' index = 0 BEOL = b'\n' Filename = "" while (index < len(Text) and \ Text[index] != b'-----BEGIN PURE-CRYPTO SIGNED MESSAGE-----'+BEOL and \ Text[index] != b'-----BEGIN PURE-CRYPTO SIGNATURE-----'+BEOL) : preface = preface + Text[index] index = index + 1 if index == len(Text) : print ("File has no signature!" + EOL) sys.exit(ERR_CORRUPT) if Text[index] == b'-----BEGIN PURE-CRYPTO SIGNATURE-----'+BEOL : # signed text is separate if sys.argv[2][-4:] == ".sig" : Filename = sys.argv[2][:-4] try: INFILE = open(Filename, "rb") except: print ("Cannot open file ", Filename) sys.exit(ERR_PERM) print ("Data is assumed to be separate in file ", Filename) MessageText = INFILE.readlines() MessageText.append(BEOL) else: # read message index = index + 1 while index < len(Text) and Text[index] != b'-----BEGIN PURE-CRYPTO SIGNATURE-----'+BEOL : MessageText.append(Text[index]) index = index + 1 # test if file may contain a signature if index >= len(Text) : print ("File has no signature!" + EOL) sys.exit(ERR_CORRUPT) try : index = index + 3 UserInformation = pure.Line(Text[index]).decode() print("Found UserID : ",str(UserInformation)) index = index + 1 EncryptedSignature = pure.toLong(Text[index]) index = index + 1 if Text[index] != b'-----END PURE-CRYPTO SIGNATURE-----'+BEOL : print ("Warning: No complete signature-block found") except : print ("Signature is corrupt!") sys.exit(ERR_CORRUPT) while index < len(Text) and Text[index] != b'-----END PURE-CRYPTO SIGNATURE-----'+BEOL : index = index + 1 index = index + 1 while index < len(Text) : epilogue = epilogue + Text[index] index = index + 1 TestMessage = b'' TestSignature = 0 for Line in MessageText : TestMessage = TestMessage + Line # remove single EOL from Testmessage TestMessage = TestMessage[:-1] print ("Input is " ,len(TestMessage), " bytes of text."+EOL) PlainHash = 0 TestHash = 1 Info = Nonce = 0 Infostring = "" SignatureNumber = 0 # select cryptosystem # select only signing keys Result = pure.select_cryptosystem(UserInformation, False) if Result : print (EOL + "Using public key [" , pure.toString(pure.UserID), "] for verification.") TestHash = pure.hash(TestMessage) SignatureNumber = pure.ModExp(EncryptedSignature, pure.Encryption, pure.Modulus) # extract Timestamp, Nonce and unblinded hash(message) BlindedHash = int(SignatureNumber % pow(2,pure.HashModulusLength)) Info = SignatureNumber // pow(2,pure.HashModulusLength) BlindNonce = int (Info % pow(2,256)) Timestamp = pure.LongListToString([Info // pow(2,256)]) # unblind the BlindNonce with the securityhash of the public signing key Nonce = BlindNonce ^ pure.Securityhash PlainHash = BlindedHash ^ pure.hash(pure.toBytes(Nonce)) if PlainHash == TestHash : print (EOL + sys.argv[2] + ": GOOD SIGNATURE made ", Timestamp , EOL + EOL) sys.exit(0) else : print (EOL + " ***BAD SIGNATURE*** in file " + sys.argv[2] + EOL + EOL) sys.exit(ERR_SIG) else: print (EOL + "The public key used to verify this signature is unavailable.") sys.exit(ERR_SIG) #-----------------------------------------------------------# # Main # #-----------------------------------------------------------# pure.print_banner() if len(sys.argv) == 3 : Options = ["-e","-d","-s","-ds","-v"] if not (sys.argv[1] in Options) : print("You must give a valid option to determine what to do with the file") print ("usage: pcp2 -e|-d|-s|-ds|-v file") sys.exit(ERR_USE) try: INFILE = open(sys.argv[2], "rb") Text = INFILE.readlines() INFILE.close() except: print ("File " + sys.argv[2] + " is not accessible." + EOL) sys.exit(ERR_PERM) # DO NOT CHANGE THE BINARY INPUT !!!! if (sys.argv[1] != "-d") : print ("Checking my signing key\'s integrity.") pure.read_cryptosystem("signingkey", True) pure.print_securityhash(True) if sys.argv[1] == "-e" : ENCRYPTION() elif sys.argv[1] == "-d" : DECRYPTION() elif sys.argv[1] == "-s" : SIGNING("clear") elif sys.argv[1] == "-ds" : SIGNING("separate") elif sys.argv[1] == "-v" : VERIFICATION() else: print ("usage: pcp2 -e|-d|-s|-ds|-v file") sys.exit(ERR_USE) else: print ("usage: pcp2 -e|-d|-s|-ds|-v file") sys.exit(ERR_USE) #-----------------------------------------------------------# # Copyright 2003 - 2025, Ralf Senderek, Ireland # #-----------------------------------------------------------# sys.exit(0)