def _generate_domain(L, randfunc): """Generate a new set of DSA domain parameters""" N = { 1024:160, 2048:224, 3072:256 }.get(L) if N is None: raise ValueError("Invalid modulus length (%d)" % L) outlen = SHA256.digest_size * 8 n = (L + outlen - 1) // outlen - 1 # ceil(L/outlen) -1 b_ = L - 1 - (n * outlen) # Generate q (A.1.1.2) q = Integer(4) upper_bit = 1 << (N - 1) while test_probable_prime(q, randfunc) != PROBABLY_PRIME: seed = randfunc(64) U = Integer.from_bytes(SHA256.new(seed).digest()) & (upper_bit - 1) q = U | upper_bit | 1 assert(q.size_in_bits() == N) # Generate p (A.1.1.2) offset = 1 upper_bit = 1 << (L - 1) while True: V = [ SHA256.new(seed + Integer(offset + j).to_bytes()).digest() for j in iter_range(n + 1) ] V = [ Integer.from_bytes(v) for v in V ] W = sum([V[i] * (1 << (i * outlen)) for i in iter_range(n)], (V[n] & ((1 << b_) - 1)) * (1 << (n * outlen))) X = Integer(W + upper_bit) # 2^{L-1} < X < 2^{L} assert(X.size_in_bits() == L) c = X % (q * 2) p = X - (c - 1) # 2q divides (p-1) if p.size_in_bits() == L and \ test_probable_prime(p, randfunc) == PROBABLY_PRIME: break offset += n + 1 # Generate g (A.2.3, index=1) e = (p - 1) // q for count in itertools.count(1): U = seed + b"ggen" + bchr(1) + Integer(count).to_bytes() W = Integer.from_bytes(SHA256.new(U).digest()) g = pow(W, e, p) if g != 1: break return (p, q, g, seed)
def _sign(self, unsigned_string): """ 通过如下方法调试签名 方法1 key = rsa.PrivateKey.load_pkcs1(open(self._app_private_key_path).read()) sign = rsa.sign(unsigned_string.encode("utf8"), key, "SHA-1") # base64 编码,转换为unicode表示并移除回车 sign = base64.encodebytes(sign).decode("utf8").replace("\n", "") 方法2 key = RSA.importKey(open(self._app_private_key_path).read()) signer = PKCS1_v1_5.new(key) signature = signer.sign(SHA.new(unsigned_string.encode("utf8"))) # base64 编码,转换为unicode表示并移除回车 sign = base64.encodebytes(signature).decode("utf8").replace("\n", "") 方法3 echo "abc" | openssl sha1 -sign alipay.key | openssl base64 """ # 开始计算签名 key = self.app_private_key signer = PKCS1_v1_5.new(key) if self._sign_type == "RSA": signature = signer.sign(SHA.new(b(unsigned_string))) else: signature = signer.sign(SHA256.new(b(unsigned_string))) # base64 编码,转换为unicode表示并移除回车 sign = encodebytes(signature).decode("utf8").replace("\n", "") return sign
def _get_filekey(self): """This method creates a key from a keyfile.""" if not os.path.exists(self.keyfile): raise KPError('Keyfile not exists.') try: with open(self.keyfile, 'rb') as handler: handler.seek(0, os.SEEK_END) size = handler.tell() handler.seek(0, os.SEEK_SET) if size == 32: return handler.read(32) elif size == 64: try: return binascii.unhexlify(handler.read(64)) except (TypeError, binascii.Error): handler.seek(0, os.SEEK_SET) sha = SHA256.new() while True: buf = handler.read(2048) sha.update(buf) if len(buf) < 2048: break return sha.digest() except IOError as e: raise KPError('Could not read file: %s' % e)
def verify(self, message: bytes, signature: bytes) -> bool: h = SHA256.new(message) try: pkcs1_15.new(self.public_key).verify(h, signature) # type: ignore return True except (ValueError, TypeError): return False
def sign_transaction(self, sender, recipient, amount): signer = PKCS1_v1_5.new(RSA.importKey( binascii.unhexlify(self.private_key))) h = SHA256.new((str(sender) + str(recipient) + str(amount)).encode('utf8')) signature = signer.sign(h) return binascii.hexlify(signature).decode('ascii')
def _transform_key(self, masterkey): """This method creates the key to decrypt the database""" aes = AES.new(self._transf_randomseed, AES.MODE_ECB) # Encrypt the created hash for _ in range(self._key_transf_rounds): masterkey = aes.encrypt(masterkey) # Finally, hash it again... sha_obj = SHA256.new() sha_obj.update(masterkey) masterkey = sha_obj.digest() # ...and hash the result together with the randomseed sha_obj = SHA256.new() sha_obj.update(self._final_randomseed + masterkey) return sha_obj.digest()
def create_pairing_response_by_serial(self, user_token_id): """ Creates a base64-encoded pairing response that identifies the token by its serial :param user_token_id: the token id (primary key for the user token db) :returns base64 encoded pairing response """ token_serial = self.tokens[user_token_id]['serial'] server_public_key = self.tokens[user_token_id]['server_public_key'] partition = self.tokens[user_token_id]['partition'] # ------------------------------------------------------------------ -- # assemble header and plaintext header = struct.pack('<bI', PAIR_RESPONSE_VERSION, partition) pairing_response = b'' pairing_response += struct.pack('<bI', TYPE_PUSHTOKEN, user_token_id) pairing_response += self.public_key pairing_response += token_serial.encode('utf8') + b'\x00\x00' pairing_response += self.gda + b'\x00' signature = crypto_sign_detached(pairing_response, self.secret_key) pairing_response += signature # ------------------------------------------------------------------ -- # create public diffie hellman component # (used to decrypt and verify the reponse) r = os.urandom(32) R = calc_dh_base(r) # ------------------------------------------------------------------ -- # derive encryption key and nonce server_public_key_dh = dsa_to_dh_public(server_public_key) ss = calc_dh(r, server_public_key_dh) U = SHA256.new(ss).digest() encryption_key = U[0:16] nonce = U[16:32] # ------------------------------------------------------------------ -- # encrypt in EAX mode cipher = AES.new(encryption_key, AES.MODE_EAX, nonce) cipher.update(header) ciphertext, tag = cipher.encrypt_and_digest(pairing_response) return encode_base64_urlsafe(header + R + ciphertext + tag)
def _verify(self, raw_content, signature): # 开始计算签名 key = self.alipay_public_key signer = PKCS1_v1_5.new(key) if self._sign_type == "RSA": digest = SHA.new() else: digest = SHA256.new() digest.update(raw_content.encode("utf8")) if signer.verify(digest, decodebytes(signature.encode("utf8"))): return True return False
def runTest(self): key = b'0' * 16 h = SHA256.new() for length in range(160): nonce = '{0:04d}'.format(length).encode('utf-8') data = bchr(length) * length cipher = AES.new(key, AES.MODE_GCM, nonce=nonce, **self._extra_params) ct, tag = cipher.encrypt_and_digest(data) h.update(ct) h.update(tag) self.assertEqual(h.hexdigest(), "7b7eb1ffbe67a2e53a912067c0ec8e62ebc7ce4d83490ea7426941349811bdf4")
def test_asn1_encoding(self): """Verify ASN.1 encoding""" self.description = "ASN.1 encoding test" hash_obj = SHA256.new() signer = DSS.new(self.key_priv, 'fips-186-3', 'der') signature = signer.sign(hash_obj) # Verify that output looks like a DER SEQUENCE self.assertEqual(bord(signature[0]), 48) signer.verify(hash_obj, signature) # Verify that ASN.1 parsing fails as expected signature = bchr(7) + signature[1:] self.assertRaises(ValueError, signer.verify, hash_obj, signature)
def runTest(self): """SHA256: 512/520 MiB test""" from Cryptodome.Hash import SHA256 zeros = bchr(0x00) * (1024*1024) h = SHA256.new(zeros) for i in range(511): h.update(zeros) # This test vector is from PyCrypto's old testdata.py file. self.assertEqual('9acca8e8c22201155389f65abbf6bc9723edc7384ead80503839f49dcc56d767', h.hexdigest()) # 512 MiB for i in range(8): h.update(zeros) # This test vector is from PyCrypto's old testdata.py file. self.assertEqual('abf51ad954b246009dfe5a50ecd582fd5b8f1b8b27f30393853c3ef721e7fa6e', h.hexdigest()) # 520 MiB
def test1(self): q = 0x4000000000000000000020108A2E0CC0D99F8A5EFL x = 0x09A4D6792295A7F730FC3F2B49CBC0F62E862272FL p = 2 * q + 1 y = pow(2, x, p) key = DSA.construct([pow(y, 2, p), 2L, p, q, x], False) signer = DSS.new(key, 'deterministic-rfc6979') # Test _int2octets self.assertEqual(hexlify(signer._int2octets(x)), b("009a4d6792295a7f730fc3f2b49cbc0f" "62e862272f")) # Test _bits2octets h1 = SHA256.new(b("sample")).digest() self.assertEqual(hexlify(signer._bits2octets(h1)), b("01795edf0d54db760f156d0dac04c032" "2b3a204224"))
def concat_sha256(secret, dk_len, other_info): """ The Concat KDF, using SHA256 as the hash function. Note: Does not validate that otherInfo meets the requirements of SP800-56A. :param secret: The shared secret value :param dk_len: Length of key to be derived, in bits :param other_info: Other info to be incorporated (see SP800-56A) :return: The derived key """ dkm = b'' dk_bytes = int(ceil(dk_len / 8.0)) counter = 0 while len(dkm) < dk_bytes: counter += 1 counter_bytes = pack("!I", counter) dkm += SHA256.new(counter_bytes + secret + other_info ).digest() return dkm[:dk_bytes]
def create_challenge_url(self, transaction_id, content_type, callback_url='', message=None, login=None, host=None): """ creates a challenge url (looking like lseqr://push/<base64string>), returns the url and the unencrypted challenge data :param transaction_id: The transaction id generated by LinOTP :param content_type: One of the types CONTENT_TYPE_SIGNREQ, CONTENT_TYPE_PAIRING, CONTENT_TYPE_LOGIN :param callback_url: callback url (optional), default is empty string :param message: the transaction message, that should be signed by the client. Only for content type CONTENT_TYPE_SIGNREQ :param login: the login name of the user. Only for content type CONTENT_TYPE_LOGIN :param host: hostname of the user. Only for content type CONTENT_TYPE_LOGIN :returns: tuple (challenge_url, sig_base), with challenge_url being the push url and sig_base the message, that is used for the client signature """ serial = self.getSerial() # ------------------------------------------------------------------- -- # sanity/format checks if content_type not in [CONTENT_TYPE_SIGNREQ, CONTENT_TYPE_PAIRING, CONTENT_TYPE_LOGIN]: raise InvalidFunctionParameter('content_type', 'content_type must ' 'be CONTENT_TYPE_SIGNREQ, ' 'CONTENT_TYPE_PAIRING or ' 'CONTENT_TYPE_LOGIN.') # ------------------------------------------------------------------- -- # after the lseqr://push/ prefix the following data is encoded # in urlsafe base64: # --------------------------------------------------- # fields | version | user token id | R | ciphertext | sign | # --------------------------------------------------- # | header | body | # --------------------------------------------------- # size | 1 | 4 | 32 | ? | 64 | # --------------------------------------------------- # # create header user_token_id = self.getFromTokenInfo('user_token_id') data_header = struct.pack('<bI', CHALLENGE_URL_VERSION, user_token_id) # ------------------------------------------------------------------- -- # create body r = urandom(32) R = calc_dh_base(r) b64_user_dsa_public_key = self.getFromTokenInfo('user_dsa_public_key') user_dsa_public_key = b64decode(b64_user_dsa_public_key) user_dh_public_key = dsa_to_dh_public(user_dsa_public_key) ss = calc_dh(r, user_dh_public_key) U = SHA256.new(ss).digest() zerome(ss) sk = U[0:16] nonce = U[16:32] zerome(U) # ------------------------------------------------------------------- -- # create plaintext section # ------------------------------------------------------------------- -- # generate plaintext header # ------------------------------------------------ # fields | content_type | transaction_id | timestamp | .. # ------------------------------------------------ # size | 1 | 8 | 8 | ? # ------------------------------------------------- transaction_id = transaction_id_to_u64(transaction_id) plaintext = struct.pack('<bQQ', content_type, transaction_id, int(time.time())) # ------------------------------------------------------------------- -- utf8_callback_url = callback_url.encode('utf8') # enforce max url length as specified in protocol if len(utf8_callback_url) > 511: raise InvalidFunctionParameter('callback_url', 'max string ' 'length (encoded as utf8) is ' '511') # ------------------------------------------------------------------- -- # create data package depending on content type # ------------------------------------------------------------------- -- if content_type == CONTENT_TYPE_PAIRING: # ----------------------------------------- # fields | header | serial | NUL | callback | NUL | # ----------------------------------------- # size | 9 | ? | 1 | ? | 1 | # ----------------------------------------- utf8_serial = serial.encode('utf8') if len(utf8_serial) > 63: raise ValueError('serial (encoded as utf8) can only be 63 ' 'characters long') plaintext += utf8_serial + b'\00' + utf8_callback_url + b'\00' # ------------------------------------------------------------------- -- if content_type == CONTENT_TYPE_SIGNREQ: if message is None: raise InvalidFunctionParameter('message', 'message must be ' 'supplied for content type ' 'SIGNREQ') # ------------------------------------------ # fields | header | message | NUL | callback | NUL | # ------------------------------------------ # size | 9 | ? | 1 | ? | 1 | # ------------------------------------------ utf8_message = message.encode('utf8') # enforce max sizes specified by protocol if len(utf8_message) > 511: raise InvalidFunctionParameter('message', 'max string ' 'length (encoded as utf8) is ' '511') plaintext += utf8_message + b'\00' + utf8_callback_url + b'\00' # ------------------------------------------------------------------- -- if content_type == CONTENT_TYPE_LOGIN: if login is None: raise InvalidFunctionParameter('login', 'login must be ' 'supplied for content type ' 'LOGIN') if host is None: raise InvalidFunctionParameter('host', 'host must be ' 'supplied for content type ' 'LOGIN') # ----------------------------------------------------- # fields | header | login | NUL | host | NUL | callback | NUL | # ----------------------------------------------------- # size | 9 | ? | 1 | ? | 1 | ? | 1 | # ----------------------------------------------------- utf8_login = login.encode('utf8') utf8_host = host.encode('utf8') # enforce max sizes specified by protocol if len(utf8_login) > 127: raise InvalidFunctionParameter('login', 'max string ' 'length (encoded as utf8) is ' '127') if len(utf8_host) > 255: raise InvalidFunctionParameter('host', 'max string ' 'length (encoded as utf8) is ' '255') plaintext += utf8_login + b'\00' plaintext += utf8_host + b'\00' plaintext += utf8_callback_url + b'\00' # ------------------------------------------------------------------- -- # encrypt inner layer nonce_as_int = int_from_bytes(nonce, byteorder='big') ctr = Counter.new(128, initial_value=nonce_as_int) cipher = AES.new(sk, AES.MODE_CTR, counter=ctr) ciphertext = cipher.encrypt(plaintext) unsigned_raw_data = data_header + R + ciphertext # ------------------------------------------------------------------- -- # create signature partition = self.getFromTokenInfo('partition') secret_key = get_secret_key(partition) signature = crypto_sign_detached(unsigned_raw_data, secret_key) raw_data = unsigned_raw_data + signature url = 'lseqr://push/' + encode_base64_urlsafe(raw_data) return url, (signature + plaintext)
def verifty_tx_sign(tx): public_key = RSA.importKey(binascii.unhexlify(tx.sender)) verifier = PKCS1_v1_5.new(public_key) h = SHA256.new((str(tx.sender) + str(tx.recipient) + str(tx.amount)).encode('utf8')) return verifier.verify(h, binascii.unhexlify(tx.signature))
def _sign(private_key: str, msg: bytes) -> bytes: key = RSA.import_key(private_key) h = SHA256.new(msg) return pkcs1_15.new(key).sign(h)
def _create_payload_hash(self, transaction): data = ("{}{}{}".format(transaction.sender, transaction.recipient, transaction.amount).encode("utf8")) return SHA256.new(data)
def decrypt_and_verify_challenge(self, challenge_url, action): """ Decrypts the data packed in the challenge url, verifies its content, returns the parsed data as a dictionary, calculates and returns the signature. The calling method must then send the signature back to the server. (The reason for this control flow is that the challenge data must be checked in different scenarios, e.g. when we have a pairing the data must be checked by the method that simulates the pairing) :param challenge_url: the challenge url as sent by the server :param action: a string identifier for the verification action (at the moment 'ACCEPT' or 'DENY') :returns: (challenge, signature) challenge has the keys * content_type - one of the three values CONTENT_TYPE_SIGNREQ, CONTENT_TYPE_PAIRING or CONTENT_TYPE_LOGIN) (all defined in this module) * transaction_id - used to identify the challenge on the server * callback_url (optional) - the url to which the challenge response should be set * user_token_id - used to identify the token in the user database for which this challenge was created depending on the content type additional keys are present * for CONTENT_TYPE_PAIRING: serial * for CONTENT_TYPE_SIGNREQ: message * for CONTENT_TYPE_LOGIN: login, host signature is the generated user signature used to respond to the challenge """ challenge_data_encoded = challenge_url[len(self.uri + '://chal/'):] challenge_data = decode_base64_urlsafe(challenge_data_encoded) # ------------------------------------------------------------------ -- # parse and verify header information in the # encrypted challenge data header = challenge_data[0:5] version, user_token_id = struct.unpack('<bI', header) self.assertEqual(version, CHALLENGE_URL_VERSION) # ------------------------------------------------------------------ -- # get token from client token database token = self.tokens[user_token_id] server_public_key = token['server_public_key'] # ------------------------------------------------------------------ -- # prepare decryption by seperating R from # ciphertext and server signature R = challenge_data[5:5 + 32] ciphertext = challenge_data[5 + 32:-64] server_signature = challenge_data[-64:] # check signature data = challenge_data[0:-64] crypto_sign_verify_detached(server_signature, data, server_public_key) # ------------------------------------------------------------------ -- # key derivation secret_key_dh = dsa_to_dh_secret(self.secret_key) ss = calc_dh(secret_key_dh, R) U = SHA256.new(ss).digest() sk = U[0:16] nonce = U[16:32] # ------------------------------------------------------------------ -- # decrypt and verify challenge nonce_as_int = int_from_bytes(nonce, byteorder='big') ctr = Counter.new(128, initial_value=nonce_as_int) cipher = AES.new(sk, AES.MODE_CTR, counter=ctr) plaintext = cipher.decrypt(ciphertext) # ------------------------------------------------------------------ -- # parse/check plaintext header # 1 - for content type # 8 - for transaction id # 8 - for time stamp offset = 1 + 8 + 8 pt_header = plaintext[0:offset] (content_type, transaction_id, _time_stamp) = struct.unpack('<bQQ', pt_header) transaction_id = u64_to_transaction_id(transaction_id) # ------------------------------------------------------------------ -- # prepare the parsed challenge data challenge = {} challenge['content_type'] = content_type # ------------------------------------------------------------------ -- # retrieve plaintext data depending on content_type if content_type == CONTENT_TYPE_PAIRING: serial, callback_url, __ = plaintext[offset:].split('\x00') challenge['serial'] = serial elif content_type == CONTENT_TYPE_SIGNREQ: message, callback_url, __ = plaintext[offset:].split('\x00') challenge['message'] = message elif content_type == CONTENT_TYPE_LOGIN: login, host, callback_url, __ = plaintext[offset:].split('\x00') challenge['login'] = login challenge['host'] = host # ------------------------------------------------------------------ -- # prepare the parsed challenge data challenge['callback_url'] = callback_url challenge['transaction_id'] = transaction_id challenge['user_token_id'] = user_token_id # calculate signature sig_base = ( struct.pack('<b', CHALLENGE_URL_VERSION) + b'%s\0' % action + server_signature + plaintext) sig = crypto_sign_detached(sig_base, self.secret_key) encoded_sig = encode_base64_urlsafe(sig) return challenge, encoded_sig
# exporting public key print(f'{"Exporting the public key...": ^40}') DSpubkey = DSrsakey_pair.publickey().exportKey() try: open("../deployment-files/DSpublickey.pem","wb").write(DSpubkey) print(f'{"Done!": ^40}') except: print(f'{"Oops! Failed to export the public key": ^40}') print(f'{"":-^40}') sys.exit(-1) # generating digest print(f'{"":-^40}') print(f'{"Generating SHA256 hash...": ^40}') digest = SHA256.new(file_bytes) print(f'{"Done!": ^40}') print(f'{"":-^40}') # generating signer signer = pkcs1_15.new(DSrsakey_pair) # signing digest print(f'{"Signing digest...": ^40}') signature = signer.sign(digest) print(f'{"Done!": ^40}') print(f'{"":-^40}') # time.sleep(3) my_socket.sendall(signature) # send signature print(f'{"Sent DIGITAL SIGNATURE": ^40}')
def get_key(password): hasher = SHA256.new(password.encode('utf-8')) return hasher.digest()
def derive_key(rounds, data): h = SHA256.new() for _ in range(rounds): h.update(data) data = h.digest() return data
def sign(o, p): hash = SHA256.new(o) return pkcs1_15.new(p).sign(hash)
def get_backup_key_digest(backup_secret): key = SHA256.new() key.update(backup_secret.encode('utf-8')) return key.digest()
def main(): from Cryptodome.Signature import pss from Cryptodome.Hash import SHA256 from Cryptodome.PublicKey import RSA import base64 import logging import os import struct logging.basicConfig() logger = logging.getLogger(os.path.basename(__file__)) args = get_args(logger) with open(args.key, 'rb') as f: key = RSA.importKey(f.read()) with open(args.inf, 'rb') as f: img = f.read() h = SHA256.new() digest_len = h.digest_size sig_len = key.size_in_bytes() img_size = len(img) hdr_version = args.ta_version # struct shdr_bootstrap_ta::ta_version magic = 0x4f545348 # SHDR_MAGIC if args.enc_key: img_type = 2 # SHDR_ENCRYPTED_TA else: img_type = 1 # SHDR_BOOTSTRAP_TA algo = 0x70414930 # TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256 shdr = struct.pack('<IIIIHH', magic, img_type, img_size, algo, digest_len, sig_len) shdr_uuid = args.uuid.bytes shdr_version = struct.pack('<I', hdr_version) if args.enc_key: from Cryptodome.Cipher import AES cipher = AES.new(bytearray.fromhex(args.enc_key), AES.MODE_GCM) ciphertext, tag = cipher.encrypt_and_digest(img) enc_algo = 0x40000810 # TEE_ALG_AES_GCM flags = 0 # SHDR_ENC_KEY_DEV_SPECIFIC ehdr = struct.pack('<IIHH', enc_algo, flags, len(cipher.nonce), len(tag)) h.update(shdr) h.update(shdr_uuid) h.update(shdr_version) if args.enc_key: h.update(ehdr) h.update(cipher.nonce) h.update(tag) h.update(img) img_digest = h.digest() def write_image_with_signature(sig): with open(args.outf, 'wb') as f: f.write(shdr) f.write(img_digest) f.write(sig) f.write(shdr_uuid) f.write(shdr_version) if args.enc_key: f.write(ehdr) f.write(cipher.nonce) f.write(tag) f.write(ciphertext) else: f.write(img) def sign_encrypt_ta(): if not key.has_private(): logger.error('Provided key cannot be used for signing, ' + 'please use offline-signing mode.') sys.exit(1) else: signer = pss.new(key) sig = signer.sign(h) if len(sig) != sig_len: raise Exception( ("Actual signature length is not equal to ", "the computed one: {} != {}").format(len(sig), sig_len)) write_image_with_signature(sig) logger.info('Successfully signed application.') def generate_digest(): with open(args.digf, 'wb+') as digfile: digfile.write(base64.b64encode(img_digest)) def stitch_ta(): try: with open(args.sigf, 'r') as sigfile: sig = base64.b64decode(sigfile.read()) except IOError: if not os.path.exists(args.digf): generate_digest() logger.error( 'No signature file found. Please sign\n %s\n' + 'offline and place the signature at \n %s\n' + 'or pass a different location ' + 'using the --sig argument.\n', args.digf, args.sigf) sys.exit(1) else: verifier = pss.new(key) if verifier.verify(h, sig): write_image_with_signature(sig) logger.info('Successfully applied signature.') else: logger.error('Verification failed, ignoring given signature.') sys.exit(1) # dispatch command { 'sign-enc': sign_encrypt_ta, 'digest': generate_digest, 'generate-digest': generate_digest, 'stitch': stitch_ta, 'stitch-ta': stitch_ta }.get(args.command, 'sign_encrypt_ta')()
def _sign(private_key, msg): key = RSA.import_key(private_key) h = SHA256.new(msg) return pkcs1_15.new(key).sign(h)
async def main(): global local_anchor import_safebag("sec/client.safebag", "1234") # parse again to read prv key into memory request = CertRequest() with open("sec/client.safebag", "r") as safebag: wire = safebag.read() wire = base64.b64decode(wire) wire = parse_and_check_tl(wire, SecurityV2TypeNumber.SAFE_BAG) bag = SafeBag.parse(wire) # attach the testbed-signed certificate request.testbed_signed = bag.certificate_v2 # parse the key bag to obtain private key testbed_signed = CertificateV2Value.parse(bag.certificate_v2) key_bag = bytes(bag.encrypted_key_bag) privateKey = serialization.load_der_private_key(key_bag, password=b'1234', backend=default_backend()) client_prv_key = privateKey.private_bytes(Encoding.DER, PrivateFormat.PKCS8, NoEncryption()) # parse trust anchor and self-assigns a name, then create self-signed certificate with open("sec/client.anchor", "r") as anchor: wire = anchor.read() wire = base64.b64decode(wire) local_anchor = parse_certificate(wire) # self-assign a name and create corresponding key pair client_name = local_anchor.name[:-4] + [testbed_signed.name[-5]] client_identity = app.keychain.touch_identity(client_name) # attach newly generated self-assigned certificate cert = client_identity.default_key().default_cert().data cert = parse_certificate(cert) request.self_signed = cert.encode() try: # express the first interest to fetch a token/secret code timestamp = ndn.utils.timestamp() name = Name.from_str('/edge/_ca/new-cert') + [Component.from_timestamp(timestamp)] logging.info(f'Sending Interest {Name.to_str(name)}, {InterestParam(must_be_fresh=True, lifetime=6000)}') data_name, meta_info, content = await app.express_interest( name, app_param=request.encode(), must_be_fresh=True, can_be_prefix=False, lifetime=6000, identity=client_identity, validator=verify_ecdsa_signature) # sign it use the private key, to prove the certificate possesion h = SHA256.new() h.update(bytes(content)) pk = ECC.import_key(client_prv_key) signature = DSS.new(pk, 'fips-186-3', 'der').sign(h) logging.info(f'Getting Data {Name.to_str(name)}, begin signing the token {bytes(content)}') # express the second interest to fetch the issued certificate name = Name.from_str('/edge/_ca/challenge') + [Component.from_timestamp(timestamp)] logging.info(f'Sending Interest {Name.to_str(name)}, {InterestParam(must_be_fresh=True, lifetime=6000)}') data_name, meta_info, content = await app.express_interest( name, app_param=signature, must_be_fresh=True, can_be_prefix=False, lifetime=6000, identity=client_identity, validator=verify_ecdsa_signature) # parse the issued certificate and install issued_cert = parse_certificate(content) logging.info("Issued certificate: %s", Name.to_str(issued_cert.name)) app.keychain.import_cert(Name.to_bytes(issued_cert.name[:-2]), Name.to_bytes(issued_cert.name), bytes(content)) except InterestNack as e: print(f'Nacked with reason={e.reason}') except InterestTimeout: print(f'Timeout') except InterestCanceled: print(f'Canceled') except ValidationFailure: print(f'Data failed to validate') app.shutdown()
def main(): action = input("Chose action (sign -- s, check -- c): ") if (action != "s" and action != "c"): print("Stop") return if (action == "s"): filename = input("Input filename: ") random_generator = Random.new().read key = RSA.generate(2048, random_generator) try: _hash = SHA256.new() with open(filename, "rb") as sign_file: for chunk in iter(lambda: sign_file.read(4096), b""): _hash.update(chunk) except FileNotFoundError: print("Cannot open file") exit(0) signature = pkcs1_15.new(key).sign(_hash) with open(filename + '.key.pem', "wb") as pkey: pkey.write(key.publickey().export_key()) with open(filename + '.signature', 'wb') as signed_file: signed_file.write(signature) print("Digital sign was successfully created") elif (action == "c"): key_file = input("Input key filename: ") sign_file = input("Input signature filename: ") check_file = input("Input filename to check: ") try: with open(key_file, "rb") as pkey: key = RSA.import_key(pkey.read()) print("Read key from " + key_file + ".key.pem") except FileNotFoundError: print("Key file not found") exit(0) try: with open(sign_file, "rb") as signed_file: signature = signed_file.read() print("Read signature") except FileNotFoundError: print("Signature file not found") exit(0) print("Hashing " + check_file + "...") try: # Получаем хэш файла _hash = SHA256.new() with open(check_file, "rb") as checked_file: for chunk in iter(lambda: checked_file.read(4096), b""): _hash.update(chunk) except (FileNotFoundError): print("Check file not found") exit(0) try: pkcs1_15.new(key.publickey()).verify(_hash, signature) print("Signature CONFIRMED. ") except (ValueError): print("Signature NOT CONFIRMED") else: print("Unknown action")
def key_hash(key): return SHA256.new(key.encode()).digest()
def decrypt_and_verify_challenge(self, challenge_url, accept=True): """ Decrypts the data packed in the challenge url, verifies its content, returns the parsed data as a dictionary, calculates and returns the signature. The calling method must then send the signature back to the server. (The reason for this control flow is that the challenge data must be checked in different scenarios, e.g. when we have a pairing the data must be checked by the method that simulates the pairing) :param challenge_url: the challenge url as sent by the server :returns: (challenge, signature) challenge has the keys * content_type - one of the three values CONTENT_TYPE_SIGNREQ, CONTENT_TYPE_PAIRING or CONTENT_TYPE_LOGIN) (all defined in this module) * transaction_id - used to identify the challenge on the server * callback_url (optional) - the url to which the challenge response should be set * user_token_id - used to identify the token in the user database for which this challenge was created depending on the content type additional keys are present * for CONTENT_TYPE_PAIRING: serial * for CONTENT_TYPE_SIGNREQ: message * for CONTENT_TYPE_LOGIN: login, host signature is the generated user signature used to respond to the challenge """ challenge_data_encoded = challenge_url[len('lseqr://chal/'):] challenge_data = decode_base64_urlsafe(challenge_data_encoded) # ------------------------------------------------------------------ -- # parse and verify header information in the # encrypted challenge data header = challenge_data[0:5] version, user_token_id = struct.unpack('<bI', header) self.assertEqual(version, CHALLENGE_URL_VERSION) # ------------------------------------------------------------------ -- # get token from client token database token = self.tokens[user_token_id] server_public_key = token['server_public_key'] # ------------------------------------------------------------------ -- # prepare decryption by seperating R from # ciphertext and server signature R = challenge_data[5:5 + 32] ciphertext = challenge_data[5 + 32:-64] server_signature = challenge_data[-64:] # check signature data = challenge_data[0:-64] crypto_sign_verify_detached(server_signature, data, server_public_key) # ------------------------------------------------------------------ -- # key derivation secret_key_dh = dsa_to_dh_secret(self.secret_key) ss = calc_dh(secret_key_dh, R) U = SHA256.new(ss).digest() sk = U[0:16] nonce = U[16:32] # ------------------------------------------------------------------ -- # decrypt and verify challenge nonce_as_int = int_from_bytes(nonce, byteorder='big') ctr = Counter.new(128, initial_value=nonce_as_int) cipher = AES.new(sk, AES.MODE_CTR, counter=ctr) plaintext = cipher.decrypt(ciphertext) # ------------------------------------------------------------------ -- # parse/check plaintext header # 1 - for content type # 8 - for transaction id # 8 - for time stamp offset = 1 + 8 + 8 pt_header = plaintext[0:offset] (content_type, transaction_id, _time_stamp) = struct.unpack('<bQQ', pt_header) transaction_id = u64_to_transaction_id(transaction_id) # ------------------------------------------------------------------ -- # prepare the parsed challenge data challenge = {} challenge['content_type'] = content_type # ------------------------------------------------------------------ -- # retrieve plaintext data depending on content_type if content_type == CONTENT_TYPE_PAIRING: serial, callback_url, __ = plaintext[offset:].split('\x00') challenge['serial'] = serial elif content_type == CONTENT_TYPE_SIGNREQ: message, callback_url, __ = plaintext[offset:].split('\x00') challenge['message'] = message elif content_type == CONTENT_TYPE_LOGIN: login, host, callback_url, __ = plaintext[offset:].split('\x00') challenge['login'] = login challenge['host'] = host # ------------------------------------------------------------------ -- # prepare the parsed challenge data challenge['callback_url'] = callback_url challenge['transaction_id'] = transaction_id challenge['user_token_id'] = user_token_id # calculate signature - dependend on reject or accept mode if accept: sig_base = ( struct.pack('<b', CHALLENGE_URL_VERSION) + b'ACCEPT\0' + server_signature + plaintext) else: sig_base = ( struct.pack('<b', CHALLENGE_URL_VERSION) + b'DENY\0' + server_signature + plaintext) sig = crypto_sign_detached(sig_base, self.secret_key) encoded_sig = encode_base64_urlsafe(sig) return challenge, encoded_sig
def key_to_store(key): return SHA256.new(key.encode()).hexdigest()
def sha256(inp, hexdigest=False): h = SHA256.new(inp) if hexdigest: return h.hexdigest() return h.digest()
def encrypt(message, publickeysrv): global DEBUG payload = [] timedelta = datetime.datetime.now() if DEBUG: print('[{}] [Main] > Generating signature.'.format( datetime.datetime.now())) #################################################################################################### myhash = SHA256.new(message) signature = PKCS1_v1_5.new(privatekeycli) signature = signature.sign(myhash) if DEBUG: print( '[{}] [Main] > Message succesefully signed with signature.'.format( datetime.datetime.now())) # signature encrypt if DEBUG: print('[{}] [Main] > Encrypting signature.'.format( datetime.datetime.now())) cipherrsa = PKCS1_OAEP.new(publickeysrv) sig = cipherrsa.encrypt(signature[:128]) sig = sig + cipherrsa.encrypt(signature[128:]) payload.append(sig) #################################################################################################### if DEBUG: print('[{}] [Main] > Generating 256 bit session key.'.format( datetime.datetime.now())) # creation 256 bit session key sessionkey = Random.new().read(32) # 256 bit # encryption AES of the message if DEBUG: print('[{}] [Main] > Encryption AES of the message.'.format( datetime.datetime.now())) iv = Random.new().read(16) # 128 bit obj = AES.new(sessionkey, AES.MODE_CFB, iv) ciphertext = iv + obj.encrypt(message) # SEND DATA payload.append(ciphertext) # encryption RSA of the session key if DEBUG: print('[{}] [Main] > Encryption RSA of the session key.'.format( datetime.datetime.now())) cipherrsa = PKCS1_OAEP.new(publickeysrv) sessionkey = cipherrsa.encrypt(sessionkey) # SEND DATA payload.append(sessionkey) payload1 = b'\x00\x01\x01\x00'.join(payload) if DEBUG: print('[{}] [Main] > Message succesefully encrypted for {} seconds.'. format(datetime.datetime.now(), (datetime.datetime.now() - timedelta).total_seconds())) payload_recieved = payload1.split(b'\x00\x01\x01\x00') if payload == payload_recieved and len(payload) == 3: if DEBUG: print('[{}] [Main] > Payload not corrupted.'.format( datetime.datetime.now())) return (payload1) else: print('[{}] [Main] > Error : Message corrupted! Payload parts {}/{}/3'. format(datetime.datetime.now(), len(payload), len(payload_recieved))) return (b'')
def load(self, buf = None): """This method opens an existing database. self.password/self.keyfile and self.filepath must be set. """ if self.password is None and self.keyfile is None: raise KPError('Need a password or keyfile') elif self.filepath is None and buf is None: raise KPError('Can only load an existing database!') if buf is None: buf = self.read_buf() # The header is 124 bytes long, the rest is content header = buf[:124] crypted_content = buf[124:] del buf # The header holds two signatures if not (struct.unpack('<I', header[:4])[0] == 0x9AA2D903 and struct.unpack('<I', header[4:8])[0] == 0xB54BFB65): del crypted_content del header raise KPError('Wrong signatures!') # Unpack the header self._enc_flag = struct.unpack('<I', header[8:12])[0] self._version = struct.unpack('<I', header[12:16])[0] self._final_randomseed = struct.unpack('<16s', header[16:32])[0] self._enc_iv = struct.unpack('<16s', header[32:48])[0] self._num_groups = struct.unpack('<I', header[48:52])[0] self._num_entries = struct.unpack('<I', header[52:56])[0] self._contents_hash = struct.unpack('<32s', header[56:88])[0] self._transf_randomseed = struct.unpack('<32s', header[88:120])[0] self._key_transf_rounds = struct.unpack('<I', header[120:124])[0] del header # Check if the database is supported if self._version & 0xFFFFFF00 != 0x00030002 & 0xFFFFFF00: del crypted_content raise KPError('Unsupported file version!') #Actually, only AES is supported. elif not self._enc_flag & 2: del crypted_content raise KPError('Unsupported file encryption!') if self.password is None: masterkey = self._get_filekey() elif self.password is not None and self.keyfile is not None: passwordkey = self._get_passwordkey() filekey = self._get_filekey() sha = SHA256.new() sha.update(passwordkey+filekey) masterkey = sha.digest() else: masterkey = self._get_passwordkey() # Create the key that is needed to... final_key = self._transform_key(masterkey) # ...decrypt the content decrypted_content = self._cbc_decrypt(final_key, crypted_content) # Check if decryption failed if ((len(decrypted_content) > 2147483446) or (len(decrypted_content) == 0 and self._num_groups > 0)): del decrypted_content del crypted_content raise KPError("Decryption failed!\nThe key is wrong or the file is" " damaged.") sha_obj = SHA256.new() sha_obj.update(decrypted_content) if not self._contents_hash == sha_obj.digest(): del masterkey del final_key raise KPError("Hash test failed.\nThe key is wrong or the file is " "damaged.") del masterkey del final_key # Read out the groups pos = 0 levels = [] cur_group = 0 group = v1Group() while cur_group < self._num_groups: # Every group is made up of single fields field_type = struct.unpack('<H', decrypted_content[:2])[0] decrypted_content = decrypted_content[2:] pos += 2 # Check if offset is alright if pos >= len(crypted_content)+124: del decrypted_content del crypted_content raise KPError('Unexpected error: Offset is out of range.[G1]') field_size = struct.unpack('<I', decrypted_content[:4])[0] decrypted_content = decrypted_content[4:] pos += 4 if pos >= len(crypted_content)+124: del decrypted_content del crypted_content raise KPError('Unexpected error: Offset is out of range.[G2]') # Finally read out the content b_ret = self._read_group_field(group, levels, field_type, field_size, decrypted_content) # If the end of a group is reached append it to the groups array if field_type == 0xFFFF and b_ret == True: group.db = self self.groups.append(group) group = v1Group() cur_group += 1 decrypted_content = decrypted_content[field_size:] if pos >= len(crypted_content)+124: del decrypted_content del crypted_content raise KPError('Unexpected error: Offset is out of range.[G1]') # Now the same with the entries cur_entry = 0 entry = v1Entry() while cur_entry < self._num_entries: field_type = struct.unpack('<H', decrypted_content[:2])[0] decrypted_content = decrypted_content[2:] pos += 2 if pos >= len(crypted_content)+124: del decrypted_content del crypted_content raise KPError('Unexpected error: Offset is out of range.[G1]') field_size = struct.unpack('<I', decrypted_content[:4])[0] decrypted_content = decrypted_content[4:] pos += 4 if pos >= len(crypted_content)+124: del decrypted_content del crypted_content raise KPError('Unexpected error: Offset is out of range.[G2]') b_ret = self._read_entry_field(entry, field_type, field_size, decrypted_content) if field_type == 0xFFFF and b_ret == True: self.entries.append(entry) if entry.group_id is None: del decrypted_content del crypted_content raise KPError("Found entry without group!") entry = v1Entry() cur_entry += 1 decrypted_content = decrypted_content[field_size:] pos += field_size if pos >= len(crypted_content)+124: del decrypted_content del crypted_content raise KPError('Unexpected error: Offset is out of range.[G1]') if self._create_group_tree(levels) is False: del decrypted_content del crypted_content return False del decrypted_content del crypted_content if self.filepath is not None: with open(self.filepath+'.lock', 'w') as handler: handler.write('') return True
def decrypt_pairing_response(enc_pairing_response): """ Parses and decrypts a pairing response into a named tuple PairingResponse consisting of * user_public_key - the user's public key * user_token_id - an id for the client to uniquely identify the token. this id is necessary, because the client could communicate with more than one linotp, so serials could overlap. * serial - the serial identifying the token in linotp * user_login - the user login name It is possible that either user_login or serial is None. Both being None is a valid response according to this function but will be considered an error in the calling method. The following parameters are needed: :param enc_pairing_response: The urlsafe-base64 encoded string received from the client The following exceptions can be raised: :raises ParameterError: If the pairing response has an invalid format :raises ValueError: If the pairing response has a different version than this implementation (currently hardcoded) :raises ValueError: If the pairing response indicates a different token type than QRToken (also hardcoded) :raises ValueError: If the pairing response field "partition" is not identical to the field "token_type" ("partition" is currently used for the token type id. It is reserved for multiple key usage in a future implementation.) :raises ValueError: If the MAC of the response didn't match :return: Parsed/decrypted PairingReponse """ data = decode_base64_urlsafe(enc_pairing_response) # ---------------------------------------------------------------------- -- # ------------------------------------------- -- # fields | version | partition | R | ciphertext | MAC | # ------------------------------------------- -- # size | 1 | 4 | 32 | ? | 16 | # ------------------------------------------- -- if len(data) < 1 + 4 + 32 + 16: raise ParameterError('Malformed pairing response') # ---------------------------------------------------------------------- -- # parse header header = data[0:5] version, partition = struct.unpack('<bI', header) if version != PAIR_RESPONSE_VERSION: raise ValueError('Unexpected pair-response version, ' 'expected: %d, got: %d' % (PAIR_RESPONSE_VERSION, version)) # ---------------------------------------------------------------------- -- R = data[5:32+5] ciphertext = data[32+5:-16] mac = data[-16:] # ---------------------------------------------------------------------- -- # calculate the shared secret # - -- secret_key = get_dh_secret_key(partition) ss = calc_dh(secret_key, R) # derive encryption key and nonce from the shared secret # zero the values from memory when they are not longer needed U = SHA256.new(ss).digest() zerome(ss) encryption_key = U[0:16] nonce = U[16:32] zerome(U) # decrypt response cipher = AES.new(encryption_key, AES.MODE_EAX, nonce) cipher.update(header) plaintext = cipher.decrypt_and_verify(ciphertext, mac) zerome(encryption_key) # ---------------------------------------------------------------------- -- # check format boundaries for type peaking # (token type specific length boundaries are checked # in the appropriate functions) plaintext_min_length = 1 if len(data) < plaintext_min_length: raise ParameterError('Malformed pairing response') # ---------------------------------------------------------------------- -- # get token type and parse decrypted response # -------------------- -- # fields | token type | ... | # -------------------- -- # size | 1 | ? | # -------------------- -- token_type = struct.unpack('<b', plaintext[0])[0] if token_type not in SUPPORTED_TOKEN_TYPES: raise ValueError('unsupported token type %d, supported types ' 'are %s' % (token_type, SUPPORTED_TOKEN_TYPES)) # ---------------------------------------------------------------------- -- # delegate the data parsing of the plaintext # to the appropriate function and return the result data_parser = get_pairing_data_parser(token_type) pairing_data = data_parser(plaintext) zerome(plaintext) # get the appropriate high level type try: token_type_as_str = INV_TOKEN_TYPES[token_type] except KeyError: raise ProgrammingError('token_type %d is in SUPPORTED_TOKEN_TYPES', 'however an appropriate mapping entry in ' 'TOKEN_TYPES is missing' % token_type) return PairingResponse(token_type_as_str, pairing_data)
def create_key(seed=None): if seed is None: return b64encode(os.urandom(AES.block_size)).decode() else: key = SHA256.new(seed.encode()).digest()[:AES.block_size] return b64encode(key).decode()
def create_challenge_url(self, transaction_id, content_type, message, callback_url, callback_sms_number, use_compression=False, reset_url=False): """ creates a challenge url (looking like lseqr://chal/<base64string>) from a challenge dictionary as provided by Challanges.create_challenge in lib.challenge the version identifier of the challenge url is currently hardcoded to 1. """ serial = self.getSerial() if content_type is None: content_type = CONTENT_TYPE_FREE # ---------------------------------------------------------------------- # sanity/format checks if content_type not in [ CONTENT_TYPE_PAIRING, CONTENT_TYPE_AUTH, CONTENT_TYPE_FREE ]: raise InvalidFunctionParameter( 'content_type', 'content_type must ' 'be CONTENT_TYPE_PAIRING, ' 'CONTENT_TYPE_AUTH or ' 'CONTENT_TYPE_FREE.') if content_type == CONTENT_TYPE_PAIRING and \ message != serial: raise InvalidFunctionParameter( 'message', 'message must be equal ' 'to serial in pairing mode') if content_type == CONTENT_TYPE_AUTH: if '@' not in message: raise InvalidFunctionParameter( 'message', 'For content type ' 'auth, message must have format ' '<login>@<server>') # ---------------------------------------------------------------------- # after the lseqr://chal/ prefix the following data is encoded # in urlsafe base64: # --------------------------------------------------- # fields | version | user token id | R | ciphertext | MAC | # --------------------------------------------------- # | header | | EAX enc data | # --------------------------------------------------- # size | 1 | 4 | 32 | ? | 16 | # --------------------------------------------------- # r = urandom(32) R = calc_dh_base(r) user_token_id = self.getFromTokenInfo('user_token_id') data_header = struct.pack('<bI', QRTOKEN_VERSION, user_token_id) # the user public key is saved as base64 in # the token info since the byte format is # incompatible with the json backend. b64_user_public_key = self.getFromTokenInfo('user_public_key') user_public_key = b64decode(b64_user_public_key) ss = calc_dh(r, user_public_key) U1 = SHA256.new(ss).digest() U2 = SHA256.new(U1).digest() zerome(ss) skA = U1[0:16] skB = U2[0:16] nonce = U2[16:32] zerome(U1) zerome(U2) # ---------------------------------------------------------------------- # create plaintext section # ---------------------------------------------------------------------- # create the bitmap for flags flags = 0 if use_compression: flags |= CHALLENGE_HAS_COMPRESSION # FIXME: sizecheck for message, callback url, sms number # wiki specs are utf-8 byte length (without \0) if callback_url is not None: flags |= CHALLENGE_HAS_URL if callback_sms_number is not None: flags |= CHALLENGE_HAS_SMS_NUMBER if (content_type == CONTENT_TYPE_PAIRING): flags |= CHALLENGE_HAS_SIGNATURE if reset_url: flags |= CHALLENGE_SHOULD_RESET_URL flags |= CHALLENGE_HAS_SIGNATURE #---------------------------------------------------------------------- # generate plaintext header # ---------------------------------------------- # fields | content_type | flags | transaction_id | ... | # ---------------------------------------------- # size | 1 | 1 | 8 | ? | # ---------------------------------------------- transaction_id = transaction_id_to_u64(transaction_id) pt_header = struct.pack('<bbQ', content_type, flags, transaction_id) plaintext = pt_header #---------------------------------------------------------------------- # create data package # ------------------------------- # fields | header | message | NUL | ... | # ------------------------------- # size | 10 | ? | 1 | ? | # ------------------------------- data_package = b'' utf8_message = message.encode('utf8') # enforce max sizes specified by protocol if content_type == CONTENT_TYPE_FREE and len(utf8_message) > 511: raise ParameterError('message (encoded as utf8) can only be 511 ' 'characters long') elif content_type == CONTENT_TYPE_PAIRING and len(utf8_message) > 63: raise InvalidFunctionParameter( 'message', 'max string length ' '(encoded as utf8) is 511 for ' 'content type PAIRING') elif content_type == CONTENT_TYPE_AUTH and len(utf8_message) > 511: raise InvalidFunctionParameter( 'message', 'max string length ' '(encoded as utf8) is 511 for ' 'content type AUTH') data_package += utf8_message + b'\x00' # ---------------------------------------------------------------------- # depending on function parameters add callback url # and/or callback sms number # ----------------------------------------------------- # fields | ... | callback url | NUL | callback sms | NUL | ... | # ----------------------------------------------------- # size | ? | ? | 1 | ? | 1 | ? | # ----------------------------------------------------- # ---------------------------------------------------------------------- if callback_url is not None: utf8_callback_url = callback_url.encode('utf8') # enforce max url length as specified in protocol if len(utf8_callback_url) > 511: raise InvalidFunctionParameter( 'callback_url', 'max string ' 'length (encoded as utf8) is ' '511') data_package += utf8_callback_url + b'\x00' # ---------------------------------------------------------------------- if callback_sms_number is not None: utf8_callback_sms_number = callback_sms_number.encode('utf8') if len(utf8_callback_sms_number) > 31: raise InvalidFunctionParameter( 'callback_sms_number', 'max string length (encoded ' 'as utf8) is 31') data_package += utf8_callback_sms_number + b'\x00' # ---------------------------------------------------------------------- if use_compression: maybe_compressed_data_package = zlib.compress(data_package, 9) else: maybe_compressed_data_package = data_package # ---------------------------------------------------------------------- # when content type is pairing the protocol specifies that # the server must send a hmac based signature with the # response sig = '' if flags & CHALLENGE_HAS_SIGNATURE: hmac_message = nonce + pt_header + maybe_compressed_data_package sig = HMAC.new(self.server_hmac_secret, hmac_message, digestmod=SHA256).digest() plaintext += sig # ---------------------------------------------------------------------- plaintext += maybe_compressed_data_package # ---------------------------------------------------------------------- user_message = nonce + pt_header + sig + data_package user_sig = HMAC.new(skB, user_message, digestmod=SHA256).digest() # the user sig will be given as urlsafe base64 in the # challenge response. for this reasons (and because we # need to serialize it into json) we convert the user_sig # into this format. user_sig = encode_base64_urlsafe(user_sig) # ---------------------------------------------------------------------- cipher = AES.new(skA, AES.MODE_EAX, nonce) cipher.update(data_header) ciphertext, tag = cipher.encrypt_and_digest(plaintext) raw_data = data_header + R + ciphertext + tag url = 'lseqr://chal/' + encode_base64_urlsafe(raw_data) return url, user_sig
private_key = DSA.generate(2048) # $ PublicKeyGeneration keySize=2048 public_key = private_key.publickey() # ------------------------------------------------------------------------------ # sign/verify # ------------------------------------------------------------------------------ print("sign/verify") message = b"message" signer = DSS.new(private_key, mode='fips-186-3') hasher = SHA256.new(message) signature = signer.sign(hasher) print("signature={}".format(signature)) print() verifier = DSS.new(public_key, mode='fips-186-3') hasher = SHA256.new(message) verifier.verify(hasher, signature) print("Signature verified (as expected)") try: hasher = SHA256.new(b"other message") verifier.verify(hasher, signature)
def sign(self, message: bytes) -> bytes: self.assert_initialized() h = SHA256.new(message) return pkcs1_15.new(self.private_key).sign(h) # type: ignore
def calculatehash(message): h = SHA256.new() h.update(message) # use hexdigest to prevent problems with control characters # e.g. \r in charcter 5, appends 4, then overwrites beginning of message with rest of digest return h.hexdigest()
def query(action=None, command=None, args=None, method='GET', location=None, data=None): ''' Make a web call to Joyent ''' user = config.get_cloud_config_value( 'user', get_configured_provider(), __opts__, search_global=False ) if not user: log.error('username is required for Joyent API requests. Please set one in your provider configuration') password = config.get_cloud_config_value( 'password', get_configured_provider(), __opts__, search_global=False ) verify_ssl = config.get_cloud_config_value( 'verify_ssl', get_configured_provider(), __opts__, search_global=False, default=True ) ssh_keyfile = config.get_cloud_config_value( 'private_key', get_configured_provider(), __opts__, search_global=False, default=True ) if not ssh_keyfile: log.error('ssh_keyfile is required for Joyent API requests. Please set one in your provider configuration') ssh_keyname = config.get_cloud_config_value( 'keyname', get_configured_provider(), __opts__, search_global=False, default=True ) if not ssh_keyname: log.error('ssh_keyname is required for Joyent API requests. Please set one in your provider configuration') if not location: location = get_location() api_host_suffix = config.get_cloud_config_value( 'api_host_suffix', get_configured_provider(), __opts__, search_global=False, default=JOYENT_API_HOST_SUFFIX ) path = get_location_path(location=location, api_host_suffix=api_host_suffix) if action: path += action if command: path += '/{0}'.format(command) log.debug('User: \'%s\' on PATH: %s', user, path) if (not user) or (not ssh_keyfile) or (not ssh_keyname) or (not location): return None timenow = datetime.datetime.utcnow() timestamp = timenow.strftime('%a, %d %b %Y %H:%M:%S %Z').strip() rsa_key = salt.crypt.get_rsa_key(ssh_keyfile, None) if HAS_M2: md = EVP.MessageDigest('sha256') md.update(timestamp.encode(__salt_system_encoding__)) digest = md.final() signed = rsa_key.sign(digest, algo='sha256') else: rsa_ = PKCS1_v1_5.new(rsa_key) hash_ = SHA256.new() hash_.update(timestamp.encode(__salt_system_encoding__)) signed = rsa_.sign(hash_) signed = base64.b64encode(signed) user_arr = user.split('/') if len(user_arr) == 1: keyid = '/{0}/keys/{1}'.format(user_arr[0], ssh_keyname) elif len(user_arr) == 2: keyid = '/{0}/users/{1}/keys/{2}'.format(user_arr[0], user_arr[1], ssh_keyname) else: log.error('Malformed user string') headers = { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Api-Version': JOYENT_API_VERSION, 'Date': timestamp, 'Authorization': 'Signature keyId="{0}",algorithm="rsa-sha256" {1}'.format( keyid, signed.decode(__salt_system_encoding__) ), } if not isinstance(args, dict): args = {} # post form data if not data: data = salt.utils.json.dumps({}) return_content = None result = salt.utils.http.query( path, method, params=args, header_dict=headers, data=data, decode=False, text=True, status=True, headers=True, verify_ssl=verify_ssl, opts=__opts__, ) log.debug('Joyent Response Status Code: %s', result['status']) if 'headers' not in result: return [result['status'], result['error']] if 'Content-Length' in result['headers']: content = result['text'] return_content = salt.utils.yaml.safe_load(content) return [result['status'], return_content]
def _get_passwordkey(self): """This method just hashes self.password.""" sha = SHA256.new() sha.update(self.password.encode('utf-8')) return sha.digest()
def auth(login, password): try: global publickeycli global privatekeycli global publickeyclipem print('[{}] [Main] > Connecting to server.'.format( datetime.datetime.now())) global server server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.connect((HOST, PORT)) except KeyboardInterrupt: print('[{}] [Main] > Closing...'.format(datetime.datetime.now())) stop(server) return '{"status": "<KEYBOARDINTERRUPT>"}' #ERR CODE 0001 except Exception as e: print('[{}] [Main] > Error : Server is unreachable : {}'.format( datetime.datetime.now(), e)) return '{"status": "<SERVERUNREACHABLE>"}' try: publickeysrvpem = server.recv(8192) # 01 ########## if DEBUG: print('[{}] [Main] > Recieved greeting packet from server.'.format( datetime.datetime.now())) server.send(publickeyclipem) # 02 ########## publickeysrv = RSA.importKey(publickeysrvpem) if DEBUG: print('[{}] [Main] > RSA key exchange completed successefully.'. format(datetime.datetime.now())) ####AUTH#### if DEBUG: print('[{}] [Main] > Starting authorization algorythm.'.format( datetime.datetime.now())) password = SHA256.new(password.encode('utf-8')).hexdigest() payload = {'login': login, 'password': password} server.send( encrypt((json.dumps(payload)).encode('utf-8'), publickeysrv)) # 03 ########## if DEBUG: print( '[{}] [Main] > Sending encrypted auth credentials to server.'. format(datetime.datetime.now())) data = server.recv(8192) # 04 ########## if DEBUG: print('[{}] [Main] > Recieved server response.'.format( datetime.datetime.now())) dataJson = decrypt(data, publickeysrv) data = json.loads(dataJson) if data['status'] == '<INVALIDCREDENTIALS>': print('[{}] [Main] > Error : Invalid login or password.'.format( datetime.datetime.now())) stop(server) return dataJson elif data['status'] == '<ALREADYONLINE>': print( '[{}] [Main] > Error : User with such nickname already online.' .format(datetime.datetime.now())) stop(server) return dataJson elif data['status'] == '<TEMPBLOCKED>': left_ban_time = data['timestamp'] print( '[{}] [Main] > Error : This account temporary blocked for {} second(s).' .format(datetime.datetime.now(), left_ban_time)) stop(server) return dataJson elif data['status'] == '<BLOCKED>': print('[{}] [Main] > Error : This account blocked.'.format( datetime.datetime.now())) stop(server) return dataJson elif data['status'] == '<SUCCESS>': print('[{}] [Main] > Authorization successeful!'.format( datetime.datetime.now())) for message in data['history'].items(): print(message[1] + '\n', end='') global listnerThread listnerThread = threading.Thread(target=listner, args=(server, )) listnerThread.start() return dataJson except Exception as e: print( '[{}] [Main] > Error : Unexpected error occured during authorization process : {}.' .format(datetime.datetime.now(), e)) stop(server) return '{"status": "<EXCEPTION>", "text": "{e}"}'.format(e) except KeyboardInterrupt: print('[{}] [Main] > Closing...'.format(datetime.datetime.now())) stop(server) return '{"status": "<KEYBOARDINTERRUPT>"}'
def create_challenge_url(self, transaction_id, content_type, callback_url='', message=None, login=None, host=None): """ creates a challenge url (looking like lseqr://push/<base64string>), returns the url and the unencrypted challenge data :param transaction_id: The transaction id generated by LinOTP :param content_type: One of the types CONTENT_TYPE_SIGNREQ, CONTENT_TYPE_PAIRING, CONTENT_TYPE_LOGIN :param callback_url: callback url (optional), default is empty string :param message: the transaction message, that should be signed by the client. Only for content type CONTENT_TYPE_SIGNREQ :param login: the login name of the user. Only for content type CONTENT_TYPE_LOGIN :param host: hostname of the user. Only for content type CONTENT_TYPE_LOGIN :returns: tuple (challenge_url, sig_base), with challenge_url being the push url and sig_base the message, that is used for the client signature """ serial = self.getSerial() # ------------------------------------------------------------------- -- # sanity/format checks if content_type not in [CONTENT_TYPE_SIGNREQ, CONTENT_TYPE_PAIRING, CONTENT_TYPE_LOGIN]: raise InvalidFunctionParameter('content_type', 'content_type must ' 'be CONTENT_TYPE_SIGNREQ, ' 'CONTENT_TYPE_PAIRING or ' 'CONTENT_TYPE_LOGIN.') # ------------------------------------------------------------------- -- # after the lseqr://push/ prefix the following data is encoded # in urlsafe base64: # --------------------------------------------------- # fields | version | user token id | R | ciphertext | sign | # --------------------------------------------------- # | header | body | # --------------------------------------------------- # size | 1 | 4 | 32 | ? | 64 | # --------------------------------------------------- # # create header user_token_id = self.getFromTokenInfo('user_token_id') data_header = struct.pack('<bI', CHALLENGE_URL_VERSION, user_token_id) # ------------------------------------------------------------------- -- # create body r = urandom(32) R = calc_dh_base(r) b64_user_dsa_public_key = self.getFromTokenInfo('user_dsa_public_key') user_dsa_public_key = b64decode(b64_user_dsa_public_key) user_dh_public_key = dsa_to_dh_public(user_dsa_public_key) ss = calc_dh(r, user_dh_public_key) U = SHA256.new(ss).digest() zerome(ss) sk = U[0:16] nonce = U[16:32] zerome(U) # ------------------------------------------------------------------- -- # create plaintext section # ------------------------------------------------------------------- -- # generate plaintext header # ------------------------------------------------ # fields | content_type | transaction_id | timestamp | .. # ------------------------------------------------ # size | 1 | 8 | 8 | ? # ------------------------------------------------- transaction_id = transaction_id_to_u64(transaction_id) plaintext = struct.pack('<bQQ', content_type, transaction_id, int(time.time())) # ------------------------------------------------------------------- -- utf8_callback_url = callback_url.encode('utf8') # enforce max url length as specified in protocol if len(utf8_callback_url) > 511: raise InvalidFunctionParameter('callback_url', 'max string ' 'length (encoded as utf8) is ' '511') # ------------------------------------------------------------------- -- # create data package depending on content type # ------------------------------------------------------------------- -- if content_type == CONTENT_TYPE_PAIRING: # ----------------------------------------- # fields | header | serial | NUL | callback | NUL | # ----------------------------------------- # size | 9 | ? | 1 | ? | 1 | # ----------------------------------------- utf8_serial = serial.encode('utf8') if len(utf8_serial) > 63: raise ValueError('serial (encoded as utf8) can only be 63 ' 'characters long') plaintext += utf8_serial + b'\00' + utf8_callback_url + b'\00' # ------------------------------------------------------------------- -- if content_type == CONTENT_TYPE_SIGNREQ: if message is None: raise InvalidFunctionParameter('message', 'message must be ' 'supplied for content type ' 'SIGNREQ') # ------------------------------------------ # fields | header | message | NUL | callback | NUL | # ------------------------------------------ # size | 9 | ? | 1 | ? | 1 | # ------------------------------------------ utf8_message = message.encode('utf8') # enforce max sizes specified by protocol if len(utf8_message) > 511: raise InvalidFunctionParameter('message', 'max string ' 'length (encoded as utf8) is ' '511') plaintext += utf8_message + b'\00' + utf8_callback_url + b'\00' # ------------------------------------------------------------------- -- if content_type == CONTENT_TYPE_LOGIN: if login is None: raise InvalidFunctionParameter('login', 'login must be ' 'supplied for content type ' 'LOGIN') if host is None: raise InvalidFunctionParameter('host', 'host must be ' 'supplied for content type ' 'LOGIN') # ----------------------------------------------------- # fields | header | login | NUL | host | NUL | callback | NUL | # ----------------------------------------------------- # size | 9 | ? | 1 | ? | 1 | ? | 1 | # ----------------------------------------------------- utf8_login = login.encode('utf8') utf8_host = host.encode('utf8') # enforce max sizes specified by protocol if len(utf8_login) > 127: raise InvalidFunctionParameter('login', 'max string ' 'length (encoded as utf8) is ' '127') if len(utf8_host) > 255: raise InvalidFunctionParameter('host', 'max string ' 'length (encoded as utf8) is ' '255') plaintext += utf8_login + b'\00' plaintext += utf8_host + b'\00' plaintext += utf8_callback_url + b'\00' # ------------------------------------------------------------------- -- # encrypt inner layer nonce_as_int = int_from_bytes(nonce, byteorder='big') ctr = Counter.new(128, initial_value=nonce_as_int) cipher = AES.new(sk, AES.MODE_CTR, counter=ctr) ciphertext = cipher.encrypt(plaintext) unsigned_raw_data = data_header + R + ciphertext # ------------------------------------------------------------------- -- # create signature partition = self.getFromTokenInfo('partition') secret_key = get_secret_key(partition) signature = crypto_sign_detached(unsigned_raw_data, secret_key) raw_data = unsigned_raw_data + signature protocol_id = config.get('mobile_app_protocol_id', 'lseqr') url = protocol_id + '://push/' + encode_base64_urlsafe(raw_data) return url, (signature + plaintext)
def save(self, filepath = None, password = None, keyfile = None): """This method saves the database. It's possible to parse a data path to an alternative file. """ if (password is None and keyfile is not None and keyfile != "" and type(keyfile) is str): self.keyfile = keyfile elif (keyfile is None and password is not None and password != "" and type(password is str)): self.password = password elif (keyfile is not None and password is not None and keyfile != "" and password != "" and type(keyfile) is str and type(password) is str): self.keyfile = keyfile self.password = password if self.read_only: raise KPError("The database has been opened read-only.") elif ((self.password is None and self.keyfile is None) or (filepath is None and self.filepath is None) or (keyfile == "" and password == "")): raise KPError("Need a password/keyfile and a filepath to save the " "file.") elif ((type(self.filepath) is not str and self.filepath is not None) or (type(self.password) is not str and self.password is not None) or (type(self.keyfile) is not str and self.keyfile is not None)): raise KPError("filepath, password and keyfile must be strings.") elif self._num_groups == 0: raise KPError("Need at least one group!") content = bytearray() # First, read out all groups for i in self.groups: # Get the packed bytes # j stands for a possible field type for j in range(1, 10): ret_save = self._save_group_field(j, i) # The field type and the size is always in front of the data if ret_save is not False: content += struct.pack('<H', j) content += struct.pack('<I', ret_save[0]) content += ret_save[1] # End of field content += struct.pack('<H', 0xFFFF) content += struct.pack('<I', 0) # Same with entries for i in self.entries: for j in range(1, 15): ret_save = self._save_entry_field(j, i) if ret_save is not False: content += struct.pack('<H', j) content += struct.pack('<I', ret_save[0]) content += ret_save[1] content += struct.pack('<H', 0xFFFF) content += struct.pack('<I', 0) # Generate new seed and new vector; calculate the new hash Random.atfork() self._final_randomseed = Random.get_random_bytes(16) self._enc_iv = Random.get_random_bytes(16) sha_obj = SHA256.new() sha_obj.update(bytes(content)) self._contents_hash = sha_obj.digest() del sha_obj # Pack the header header = bytearray() header += struct.pack('<I', 0x9AA2D903) header += struct.pack('<I', 0xB54BFB65) header += struct.pack('<I', self._enc_flag) header += struct.pack('<I', self._version) header += struct.pack('<16s', self._final_randomseed) header += struct.pack('<16s', self._enc_iv) header += struct.pack('<I', self._num_groups) header += struct.pack('<I', self._num_entries) header += struct.pack('<32s', self._contents_hash) header += struct.pack('<32s', self._transf_randomseed) if self._key_transf_rounds < 150000: self._key_transf_rounds = 150000 header += struct.pack('<I', self._key_transf_rounds) # Finally encrypt everything... if self.password is None: masterkey = self._get_filekey() elif self.password is not None and self.keyfile is not None: passwordkey = self._get_passwordkey() filekey = self._get_filekey() sha = SHA256.new() sha.update(passwordkey+filekey) masterkey = sha.digest() else: masterkey = self._get_passwordkey() final_key = self._transform_key(masterkey) encrypted_content = self._cbc_encrypt(content, final_key) del content del masterkey del final_key # ...and write it out if filepath is not None: try: handler = open(filepath, "wb") except IOError: raise KPError("Can't open {0}".format(filepath)) if self.filepath is None: self.filepath = filepath elif filepath is None and self.filepath is not None: try: handler = open(self.filepath, "wb") except IOError: raise KPError("Can't open {0}".format(self.filepath)) else: raise KPError("Need a filepath.") try: handler.write(header+encrypted_content) except IOError: raise KPError("Can't write to file.") finally: handler.close() if not path.isfile(self.filepath+".lock"): try: lock = open(self.filepath+".lock", "w") lock.write('') except IOError: raise KPError("Can't create lock-file {0}".format(self.filepath +".lock")) else: lock.close() return True
def main(): myDomain = input( "Step1 Please enter a domainname to generate a certificate: ") mytime = str(int(round(time.time() * 1000))) randomEvidenceMerkleRoot = hashlib.sha256(mytime.encode()).hexdigest() #considering evidences collected #CertificateKey private_key = RSA.generate(2048) public_key = private_key.publickey() #print(private_key.exportKey(format='PEM')) #print(public_key.exportKey(format='PEM')) with open("GeneratedCerts\\" + myDomain + mytime + "PrivateKeyRSA.pem", "w") as prv_file: print("{}".format(private_key.exportKey()), file=prv_file) prv_file.close() with open("GeneratedCerts\\" + myDomain + mytime + "PublicKeyRSA.pem", "w") as pub_file: print("{}".format(public_key.exportKey()), file=pub_file) pub_file.close() PublicKeyFileName = "GeneratedCerts\\" + myDomain + mytime + "PublicKeyRSA.pem" myPublicKeyinJsonFriendlyPem = open(PublicKeyFileName, "r").read().replace('b\'' '', '').replace( '\'' '', '') print("Step2 is finilized: Web key for the certificate is generated") #DomainKey d_private_key = RSA.generate(2048) d_public_key = d_private_key.publickey() #print(d_private_key.exportKey(format='PEM')) #print(d_public_key.exportKey(format='PEM')) with open( "GeneratedCerts\\" + myDomain + mytime + "DomainPrivateKeyRSA.pem", "w") as d_prv_file: print("{}".format(d_private_key.exportKey()), file=d_prv_file) d_prv_file.close() with open( "GeneratedCerts\\" + myDomain + mytime + "DomainPublicKeyRSA.pem", "w") as d_pub_file: print("{}".format(d_public_key.exportKey()), file=d_pub_file) d_pub_file.close() print("Step3 is finilized: Domain key for the CVR is generated") myRandomIdentifier = os.urandom(8).hex() #Random Nonce 8 Bytes cert = {} cert['V'] = '4' cert['H'] = 'SHA256' cert['VT'] = '2020-12-25T13:28:06.419Z' cert['CN'] = myDomain cert['PublicKey'] = myPublicKeyinJsonFriendlyPem json_data = json.dumps(cert) certhash = hashlib.sha256(json_data.encode()).hexdigest() with open( "GeneratedCerts\\" + myDomain + mytime + "CertificateFileWithPublicKeyRSAUnsigned.jcrt", "w") as jcrt_file: print("{}".format(json_data), file=jcrt_file) certhash_bytes = bytes(certhash, "utf-8") signHash = SHA256.new(data=certhash_bytes) signer = PKCS1_v1_5.new(d_private_key) signature = signer.sign(signHash) cvr = {} cvr['C'] = cert cvr['I'] = myRandomIdentifier cvr['S'] = str(signature.hex()) cvr_json_data = json.dumps(cvr) with open( "GeneratedCerts\\" + myDomain + mytime + "CVRFileWithPublicKeyRSASigned.jcrt", "w") as cvr_jcrt_file: print("{}".format(cvr_json_data), file=cvr_jcrt_file) print( "Step4 is finilized: Certificate W/O Proof and Signed CVR are generated" ) print("The Identifier for the APKME and domain publickey locator is: " + myRandomIdentifier) mariadb_connection = mariadb.connect(user='******', password='******', host='consensuspkidbhost', database='consensuspki') mariadb_connection2 = mariadb.connect(user='******', password='******', host='consensuspkidbhost', database='consensuspki') cursor = mariadb_connection.cursor() insertCursor = mariadb_connection2.cursor() cursor.execute("select random_hash, domain from vw_tmp_random") print("Step5 is finilized: Random domains are selected") mt = merkletools.MerkleTools() certificateMerkleTree = [] myCertificateRandomPosition = (random.randint(1, 1023) * 2) # in the ConsensusPKI the certificates are placed in the certificates merkle tree based on the lexicographic order of the the hash values of the certificates #myCertificateRandomPosition = int(random.randint(1,7) * 2) ; # in the PoC we used the random order to demonstrate the process i = 0 for random_hash, domain in cursor: if i == myCertificateRandomPosition: certificateMerkleTree.append(certhash) certificateMerkleTree.append(randomEvidenceMerkleRoot) certificateMerkleTree.append( format(random_hash).replace("'", "").replace(",", "").replace( "(", "").replace(")", "")) mySubject = calculate_hash( format(domain).replace("'", "").replace(",", "").replace( "(", "").replace(")", "").encode()) insertCursor.execute( "insert into subjects (YEAR,HEIGHT,SUBJECT) values (%s,%s,%s)", ('2020', '0', mySubject)) insertCursor.execute( "insert into subjects (YEAR,HEIGHT,SUBJECT) values (%s,%s,%s)", ('2020', '0', calculate_hash(myDomain.encode()))) print( "Step6 is finilized: The certificate is placed in the Merkle tree" ) else: certificateMerkleTree.append( format(random_hash).replace("'", "").replace(",", "").replace( "(", "").replace(")", "")) mySubject = calculate_hash( format(domain).replace("'", "").replace(",", "").replace( "(", "").replace(")", "").encode()) insertCursor.execute( "insert into subjects (YEAR,HEIGHT,SUBJECT) values (%s,%s,%s)", ('2020', '0', mySubject)) i = i + 1 #print(certificateMerkleTree); mt.add_leaf(certificateMerkleTree, False) mt.make_tree() #print(mt.get_leaf_count()); root = mt.get_merkle_root() print( "Step7 is finilized. The merkle tree is constructed. The position of the certificate is: " + str(myCertificateRandomPosition)) print("Step8 is finilized: Merkle Root of Certificate No: " + str(myCertificateRandomPosition)) print(root) proof = mt.get_proof(myCertificateRandomPosition) print("Step 8: the Proof of Certificate No: " + str(myCertificateRandomPosition)) print(proof) certWithProof = {} certWithProof['C'] = cert certWithProof['P'] = proof json_data_with_proof = json.dumps(certWithProof) with open( "GeneratedCerts\\" + myDomain + mytime + "CertificateFileWithPublicKeyRSAWithProof.jcrt", "w") as jcrt_file_with_proof: print("{}".format(json_data_with_proof), file=jcrt_file_with_proof) #print(int(round(time.time() * 1000))); targetHash = mt.get_leaf(myCertificateRandomPosition) #print("Targethash"); #print(targetHash); #print("Certhash"); #print(certhash); merkleRoot = root print( "Step 9 and 10 are finilized: All subjects are inserted into the subjects table" ) insertCursor.execute( "insert into certificateblockchain (Year,Height,PreviousBlockHeader,BlockHeader,MerkleRoot,Nonce,BlockTimestamp) values (%s,%s,%s,%s,%s,%s,now())", ('2020', '0', 'p', 'b', root, '1')) print( "Step 11 is finilized: the record is inserted into the certificateblockchain table" ) print("Step 12: Updating the temporary fields to start PoW") insertCursor.execute( "update subjects s set height = (select max(height) + 1 from certificateblockchain c where c.year = s.YEAR) where s.year = 2020 and s.height =0" ) insertCursor.execute( "update certificateblockchain s set height = (select max(height) + 1 from certificateblockchain c where c.year = s.YEAR) , s.PreviousBlockHeader = (select BlockHeader from certificateblockchain t where t.year = s.YEAR and t.height = (select max(height) from certificateblockchain z where t.year = z.YEAR)) where s.year = 2020 and s.height =0" ) print("Step 12: PoW is Started") insertCursor.execute( "call PoWCertificateBlockchain(2020,(select max(c.Height) from certificateblockchain c where c.Year = 2020),@nonce, @tstamp, @bheader)" ) insertCursor.execute( "update certificateblockchain a set a.Nonce = @nonce, a.BlockTimestamp = @tstamp, a.BlockHeader =@bheader where a.Year= 2020 and a.Height = (select max(c.Height) from certificateblockchain c where c.Year = 2020)" ) print("Step 12 is finilized: PoW is Finilized") mariadb_connection2.commit() print("Fingerprint of the Certificate No: " + str(myCertificateRandomPosition)) print(targetHash) print( "Step 13 is finilized: The certificate and all related records are generated" ) is_valid = mt.validate_proof(proof, targetHash, merkleRoot) #print (is_valid); #print (merkleRoot); CalculatedMerkelRoot = get_MerkleRootFromProof(targetHash, proof) #print (CalculatedMerkelRoot); print("The validation result of the Merkle root in the certificate file") print(CalculatedMerkelRoot == root)
def test_very_long_data(self): cipher = AES.new(b'A' * 32, AES.MODE_CTR, nonce=b'') ct = cipher.encrypt(b'B' * 1000000) digest = SHA256.new(ct).hexdigest() self.assertEqual(digest, "96204fc470476561a3a8f3b6fe6d24be85c87510b638142d1d0fb90989f8a6a6")
def verify_transaction(transaction): public_key = RSA.importKey(binascii.unhexlify(transaction.sender)) verifier = PKCS1_v1_5.new(public_key) h = SHA256.new((str(transaction.sender) + str(transaction.recipient) + str(transaction.amount)).encode('utf8')) return verifier.verify(h, binascii.unhexlify(transaction.signature))
private_key = key.exportKey() ''' Encrypt the message ''' #%% cipher = PKCS1_OAEP.new(pub_key) ciphertext = cipher.encrypt(secret_message) ''' Decrypt the message ''' #%% decipher = PKCS1_OAEP.new(key) print(decipher.decrypt(ciphertext)) ''' Signing the messages ''' #%% from Cryptodome.Hash import SHA256 ''' Generate Hash code''' #%% h = SHA256.new(secret_message) hd = h.hexdigest() print(hd) ''' sign the message ''' #%% from Cryptodome.Signature import PKCS1_v1_5 signer = PKCS1_v1_5.new(key) signature = signer.sign(h) print(signature) ''' verify the signature ''' #%% PKCS1_v1_5.new(pub_key).verify(h, signature)
def decrypt_pairing_response(enc_pairing_response): """ Parses and decrypts a pairing response into a named tuple PairingResponse consisting of * user_public_key - the user's public key * user_token_id - an id for the client to uniquely identify the token. this id is necessary, because the client could communicate with more than one linotp, so serials could overlap. * serial - the serial identifying the token in linotp * user_login - the user login name It is possible that either user_login or serial is None. Both being None is a valid response according to this function but will be considered an error in the calling method. The following parameters are needed: :param enc_pairing_response: The urlsafe-base64 encoded string received from the client The following exceptions can be raised: :raises ParameterError: If the pairing response has an invalid format :raises ValueError: If the pairing response has a different version than this implementation (currently hardcoded) :raises ValueError: If the pairing response indicates a different token type than QRToken (also hardcoded) :raises ValueError: If the pairing response field "partition" is not identical to the field "token_type" ("partition" is currently used for the token type id. It is reserved for multiple key usage in a future implementation.) :raises ValueError: If the MAC of the response didn't match :return: Parsed/decrypted PairingReponse """ data = decode_base64_urlsafe(enc_pairing_response) # ---------------------------------------------------------------------- -- # ------------------------------------------- -- # fields | version | partition | R | ciphertext | MAC | # ------------------------------------------- -- # size | 1 | 4 | 32 | ? | 16 | # ------------------------------------------- -- if len(data) < 1 + 4 + 32 + 16: raise ParameterError('Malformed pairing response') # ---------------------------------------------------------------------- -- # parse header header = data[0:5] version, partition = struct.unpack('<bI', header) if version != PAIR_RESPONSE_VERSION: raise ValueError('Unexpected pair-response version, ' 'expected: %d, got: %d' % (PAIR_RESPONSE_VERSION, version)) # ---------------------------------------------------------------------- -- R = data[5:32 + 5] ciphertext = data[32 + 5:-16] mac = data[-16:] # ---------------------------------------------------------------------- -- # calculate the shared secret # - -- secret_key = get_dh_secret_key(partition) ss = calc_dh(secret_key, R) # derive encryption key and nonce from the shared secret # zero the values from memory when they are not longer needed U = SHA256.new(ss).digest() zerome(ss) encryption_key = U[0:16] nonce = U[16:32] zerome(U) # decrypt response cipher = AES.new(encryption_key, AES.MODE_EAX, nonce) cipher.update(header) plaintext = cipher.decrypt_and_verify(ciphertext, mac) zerome(encryption_key) # ---------------------------------------------------------------------- -- # check format boundaries for type peaking # (token type specific length boundaries are checked # in the appropriate functions) plaintext_min_length = 1 if len(data) < plaintext_min_length: raise ParameterError('Malformed pairing response') # ---------------------------------------------------------------------- -- # get token type and parse decrypted response # -------------------- -- # fields | token type | ... | # -------------------- -- # size | 1 | ? | # -------------------- -- token_type = struct.unpack('<b', plaintext[0])[0] if token_type not in SUPPORTED_TOKEN_TYPES: raise ValueError('unsupported token type %d, supported types ' 'are %s' % (token_type, SUPPORTED_TOKEN_TYPES)) # ---------------------------------------------------------------------- -- # delegate the data parsing of the plaintext # to the appropriate function and return the result data_parser = get_pairing_data_parser(token_type) pairing_data = data_parser(plaintext) zerome(plaintext) # get the appropriate high level type try: token_type_as_str = INV_TOKEN_TYPES[token_type] except KeyError: raise ProgrammingError( 'token_type %d is in SUPPORTED_TOKEN_TYPES', 'however an appropriate mapping entry in ' 'TOKEN_TYPES is missing' % token_type) return PairingResponse(token_type_as_str, pairing_data)
def signer(privateKey , publicKey ,menuEncoded): data = menuEncoded hashedMessage = SHA256.new(data) signature = pss.new(privateKey).sign(hashedMessage) return signature
def create_challenge_url(self, transaction_id, content_type, message, callback_url, callback_sms_number, use_compression=False, reset_url=False): """ creates a challenge url (looking like lseqr://chal/<base64string>) from a challenge dictionary as provided by Challanges.create_challenge in lib.challenge the version identifier of the challenge url is currently hardcoded to 1. """ serial = self.getSerial() if content_type is None: content_type = CONTENT_TYPE_FREE # ---------------------------------------------------------------------- # sanity/format checks if content_type not in [CONTENT_TYPE_PAIRING, CONTENT_TYPE_AUTH, CONTENT_TYPE_FREE]: raise InvalidFunctionParameter('content_type', 'content_type must ' 'be CONTENT_TYPE_PAIRING, ' 'CONTENT_TYPE_AUTH or ' 'CONTENT_TYPE_FREE.') if content_type == CONTENT_TYPE_PAIRING and \ message != serial: raise InvalidFunctionParameter('message', 'message must be equal ' 'to serial in pairing mode') if content_type == CONTENT_TYPE_AUTH: if '@' not in message: raise InvalidFunctionParameter('message', 'For content type ' 'auth, message must have format ' '<login>@<server>') # ---------------------------------------------------------------------- # after the lseqr://chal/ prefix the following data is encoded # in urlsafe base64: # --------------------------------------------------- # fields | version | user token id | R | ciphertext | MAC | # --------------------------------------------------- # | header | | EAX enc data | # --------------------------------------------------- # size | 1 | 4 | 32 | ? | 16 | # --------------------------------------------------- # r = urandom(32) R = calc_dh_base(r) user_token_id = self.getFromTokenInfo('user_token_id') data_header = struct.pack('<bI', QRTOKEN_VERSION, user_token_id) # the user public key is saved as base64 in # the token info since the byte format is # incompatible with the json backend. b64_user_public_key = self.getFromTokenInfo('user_public_key') user_public_key = b64decode(b64_user_public_key) ss = calc_dh(r, user_public_key) U1 = SHA256.new(ss).digest() U2 = SHA256.new(U1).digest() zerome(ss) skA = U1[0:16] skB = U2[0:16] nonce = U2[16:32] zerome(U1) zerome(U2) # ---------------------------------------------------------------------- # create plaintext section # ---------------------------------------------------------------------- # create the bitmap for flags flags = 0 if use_compression: flags |= CHALLENGE_HAS_COMPRESSION # FIXME: sizecheck for message, callback url, sms number # wiki specs are utf-8 byte length (without \0) if callback_url is not None: flags |= CHALLENGE_HAS_URL if callback_sms_number is not None: flags |= CHALLENGE_HAS_SMS_NUMBER if (content_type == CONTENT_TYPE_PAIRING): flags |= CHALLENGE_HAS_SIGNATURE if reset_url: flags |= CHALLENGE_SHOULD_RESET_URL flags |= CHALLENGE_HAS_SIGNATURE #---------------------------------------------------------------------- # generate plaintext header # ---------------------------------------------- # fields | content_type | flags | transaction_id | ... | # ---------------------------------------------- # size | 1 | 1 | 8 | ? | # ---------------------------------------------- transaction_id = transaction_id_to_u64(transaction_id) pt_header = struct.pack('<bbQ', content_type, flags, transaction_id) plaintext = pt_header #---------------------------------------------------------------------- # create data package # ------------------------------- # fields | header | message | NUL | ... | # ------------------------------- # size | 10 | ? | 1 | ? | # ------------------------------- data_package = b'' utf8_message = message.encode('utf8') # enforce max sizes specified by protocol if content_type == CONTENT_TYPE_FREE and len(utf8_message) > 511: raise ParameterError('message (encoded as utf8) can only be 511 ' 'characters long') elif content_type == CONTENT_TYPE_PAIRING and len(utf8_message) > 63: raise InvalidFunctionParameter('message', 'max string length ' '(encoded as utf8) is 511 for ' 'content type PAIRING') elif content_type == CONTENT_TYPE_AUTH and len(utf8_message) > 511: raise InvalidFunctionParameter('message', 'max string length ' '(encoded as utf8) is 511 for ' 'content type AUTH') data_package += utf8_message + b'\x00' # ---------------------------------------------------------------------- # depending on function parameters add callback url # and/or callback sms number # ----------------------------------------------------- # fields | ... | callback url | NUL | callback sms | NUL | ... | # ----------------------------------------------------- # size | ? | ? | 1 | ? | 1 | ? | # ----------------------------------------------------- # ---------------------------------------------------------------------- if callback_url is not None: utf8_callback_url = callback_url.encode('utf8') # enforce max url length as specified in protocol if len(utf8_callback_url) > 511: raise InvalidFunctionParameter('callback_url', 'max string ' 'length (encoded as utf8) is ' '511') data_package += utf8_callback_url + b'\x00' # ---------------------------------------------------------------------- if callback_sms_number is not None: utf8_callback_sms_number = callback_sms_number.encode('utf8') if len(utf8_callback_sms_number) > 31: raise InvalidFunctionParameter('callback_sms_number', 'max string length (encoded ' 'as utf8) is 31') data_package += utf8_callback_sms_number + b'\x00' # ---------------------------------------------------------------------- if use_compression: maybe_compressed_data_package = zlib.compress(data_package, 9) else: maybe_compressed_data_package = data_package # ---------------------------------------------------------------------- # when content type is pairing the protocol specifies that # the server must send a hmac based signature with the # response sig = '' if flags & CHALLENGE_HAS_SIGNATURE: hmac_message = nonce + pt_header + maybe_compressed_data_package sig = HMAC.new(self.server_hmac_secret, hmac_message, digestmod=SHA256).digest() plaintext += sig # ---------------------------------------------------------------------- plaintext += maybe_compressed_data_package # ---------------------------------------------------------------------- user_message = nonce + pt_header + sig + data_package user_sig = HMAC.new(skB, user_message, digestmod=SHA256).digest() # the user sig will be given as urlsafe base64 in the # challenge response. for this reasons (and because we # need to serialize it into json) we convert the user_sig # into this format. user_sig = encode_base64_urlsafe(user_sig) # ---------------------------------------------------------------------- cipher = AES.new(skA, AES.MODE_EAX, nonce) cipher.update(data_header) ciphertext, tag = cipher.encrypt_and_digest(plaintext) raw_data = data_header + R + ciphertext + tag url = 'lseqr://chal/' + encode_base64_urlsafe(raw_data) return url, user_sig
def sha256sum(data: bytes) -> str: """ returns the SHA256 hash of the provided data """ h = SHA256.new() h.update(data) return h.hexdigest()
def decrypt_pairing_response(enc_pairing_response): """ Parses and decrypts a pairing response into a named tuple PairingResponse consisting of * user_public_key - the user's public key * user_token_id - an id for the client to uniquely identify the token. this id is necessary, because the client could communicate with more than one linotp, so serials could overlap. * serial - the serial identifying the token in linotp * user_login - the user login name It is possible that either user_login or serial is None. Both being None is a valid response according to this function but will be considered an error in the calling method. The following parameters are needed: :param enc_pairing_response: The urlsafe-base64 encoded string received from the client The following exceptions can be raised: :raises ParameterError: If the pairing response has an invalid format :raises ValueError: If the pairing response has a different version than this implementation (currently hardcoded) :raises ValueError: If the pairing response indicates a different token type than QRToken (also hardcoded) :raises ValueError: If the MAC of the response didn't match :return: Parsed/encrpted PairingReponse """ data = decode_base64_urlsafe(enc_pairing_response) # -------------------------------------------------------------------------- # ----------------------- # fields | R | ciphertext | MAC | # ----------------------- # size | 32 | ? | 16 | # ----------------------- if len(data) < 32 + 16: raise ParameterError('Malformed pairing response') R = data[0:32] ciphertext = data[32:-16] mac = data[-16:] # -------------------------------------------------------------------------- # calculate the shared secret # ---- secret_key = get_qrtoken_secret_key() ss = calc_dh(secret_key, R) # derive encryption key and nonce from the shared secret # zero the values from memory when they are not longer needed U = SHA256.new(ss).digest() zerome(ss) encryption_key = U[0:16] nonce = U[16:32] zerome(U) # decrypt response cipher = AES.new(encryption_key, AES.MODE_EAX, nonce) plaintext = cipher.decrypt_and_verify(ciphertext, mac) # -------------------------------------------------------------------------- # parse decrypted response # ---- plaintext_min_length = 1 + 1 + 4 + 32 + 1 if len(data) < plaintext_min_length: raise ParameterError('Malformed pairing response') # Parse Pairing Reponse Header (First 6 Bytes) # ------------------------------------------- # fields | version | type | user token id | ... | # ------------------------------------------- # size | 1 | 1 | 4 | ? | # ------------------------------------------- resp_header = plaintext[0:6] version, token_type, user_token_id = struct.unpack('<bbI', resp_header) if version != 0: raise ValueError('Unexpected pair-response version, ' 'expected: %d, got: %d' % (0, version)) if token_type != 2: raise ValueError('wrong token type in user response, ' 'expected: %d, got: %d' % (2, token_type)) # -------------------------------------------------------------------------- # get user public key (next 32 bytes) # ----------------------------- # fields | ... | user public key | ... | # ----------------------------- # size | 6 | 32 | ? | # ----------------------------- user_public_key = plaintext[6:6+32] # -------------------------------------------------------------------------- # get serial and/or user login # --------------------------------- # fields | ... | serial | NUL | user login | # --------------------------------- # size | 38 | ? | 1 | ? | # --------------------------------- # parse token_serial and user identification serial_user_data = plaintext[6+32:].split(b'\x00') serial = serial_user_data[0].decode('utf8') user_login = serial_user_data[1].decode('utf8') return PairingResponse(user_public_key, user_token_id, serial, user_login)