Find Hive Users Memo Key and Send Encrypted Message

By completenoobs on 6/16/2025

=Intro=

Learning about hive by tinkering

  • Find The Memo Public Key from username
  • Encrypt a Message so only the private key holder of that public key can decrypt

==Creating Container==

  • Creating a Container so we know reproducable (depends installed ... etc)

  • create container

lxc launch ubuntu:24.04 keys

  • Login to container

lxc exec keys bash

  • switch to user ubuntu

su - ubuntu

==Update Container and Install Dependencies==

  • Update package lists

sudo apt update && sudo apt upgrade -y

  • Install essential packages

sudo apt install -y python3 python3-pip python3-venv git build-essential

  • Install development libraries needed for compilation

sudo apt install -y libssl-dev libffi-dev python3-dev

  • Verify Python installation

python3 --version
pip3 --version

==Create Virtual Environment (Recommended) ==

  • Create a virtual environment

python3 -m venv hive_beem_env

  • Activate the virtual environment

source hive_beem_env/bin/activate

  • Verify you're in the virtual environment (should show the path)
(hive_beem_env) ubuntu@hive:~$ 

==Install Beem==

  • NOTE: Beem is installed in Virtual Environment to avoid error: externally-managed-environment
  • Install beem from PyPI

pip install beem

  • Verify installation

python -c "import beem; print(beem.version)"

=Find a Hive Users Public Keys=

  • Create Script

$EDITOR find-user-keys.py

#!/usr/bin/env python3
import argparse
import logging
from beem import Hive
from beem.account import Account
from beem.exceptions import AccountDoesNotExistsException

# Set up logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

def get_public_keys(username, nodes=None):
    """Fetch public keys for a Hive account."""
    if nodes is None:
        nodes = [
            "https://api.hive.blog",
            "https://rpc.ecency.com",
            "https://api.deathwing.me",
            "https://fin.hive.3speak.co"
        ]
    
    try:
        # Initialize Hive instance
        hive = Hive(node=nodes)
        logger.info(f"Using node: {hive.rpc.url}")
        
        # Get account
        logger.info(f"Fetching account: {username}")
        account = Account(username, blockchain_instance=hive)
        
        # Get public keys
        keys = {
            "posting": account["posting"]["key_auths"][0][0] if account["posting"]["key_auths"] else "None",
            "active": account["active"]["key_auths"][0][0] if account["active"]["key_auths"] else "None",
            "owner": account["owner"]["key_auths"][0][0] if account["owner"]["key_auths"] else "None",
            "memo": account["memo_key"] if account["memo_key"] else "None"
        }
        
        # Print keys
        print(f"\nPublic Keys for @{username}:")
        for key_type, key_value in keys.items():
            print(f"{key_type.capitalize()}: {key_value}")
        
        # Save to file
        with open(f"{username}_public_keys.txt", "w", encoding="utf-8") as f:
            f.write(f"Public Keys for @{username}:\n")
            for key_type, key_value in keys.items():
                f.write(f"{key_type.capitalize()}: {key_value}\n")
        print(f"Saved keys to {username}_public_keys.txt")
        
        return keys
    
    except AccountDoesNotExistsException:
        logger.error(f"Account does not exist: {username}")
        print(f"Error: The account '{username}' does not exist on the Hive blockchain.")
    except Exception as e:
        logger.error(f"Error fetching keys for {username}: {str(e)}", exc_info=True)
        print(f"Error fetching keys: {str(e)}")
        print("Possible issues: node connectivity or API limits.")

def main():
    # Set up argument parser
    parser = argparse.ArgumentParser(description="Fetch public keys for a Hive account.")
    parser.add_argument("--username", required=True, help="Hive username (e.g., completenoobs)")
    args = parser.parse_args()
    
    # Fetch keys
    get_public_keys(args.username)

if __name__ == "__main__":
    main()
  • To use script

python3 find-user-keys.py --username completenoobs

  • OutPut:
(hive_beem_env) ubuntu@keys:~$ python3 find-user-keys.py --user completenoobs
2025-06-16 02:55:48,890 - INFO - Using node: https://api.hive.blog
2025-06-16 02:55:48,890 - INFO - Fetching account: completenoobs

Public Keys for @completenoobs:
Posting: STM52HfmA7gmnAjq8eQbAPgTxijsoVwm3T439dgeVqjC4baXUyJSV
Active: STM4zGfF1K9TbMoCcE2eLtTwrBwVybEhgu5yfBv99nhR6kjf5Hv97
Owner: STM8jjM8CowxH6ttosaUXCK5NufZQMga18KRD6vq1LrmTH4u4poQW
Memo: STM5u4bQRcCFfbGgg29TabvAmYsMdM6eGK8sj7sJDWPPpz6SwAums
Saved keys to completenoobs_public_keys.txt
  • Will also create a text file container user keys in same directory script run:

=Encrypting and Decrypting=

  • Install Missing Depends

pip install cryptography

  • Create Script

$EDITOR crypt.py

#!/usr/bin/env python3
"""
Offline Hive Memo Encryption/Decryption
Encrypts to public key and decrypts with private key without network access
Compatible with Hive memo format
"""

import hashlib
import hmac
import struct
import base64
import os
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, PublicFormat, NoEncryption

# Base58 alphabet used by Bitcoin/Hive
BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'

def base58_encode(data):
    """Encode bytes to base58"""
    count = 0
    for byte in data:
        if byte == 0:
            count += 1
        else:
            break
    
    encoded = ''
    num = int.from_bytes(data, 'big')
    while num > 0:
        num, remainder = divmod(num, 58)
        encoded = BASE58_ALPHABET[remainder] + encoded
    
    return '1' * count + encoded

def base58_decode(s):
    """Decode base58 to bytes"""
    count = 0
    for char in s:
        if char == '1':
            count += 1
        else:
            break
    
    num = 0
    for char in s:
        num = num * 58 + BASE58_ALPHABET.index(char)
    
    decoded = num.to_bytes((num.bit_length() + 7) // 8, 'big')
    return b'\x00' * count + decoded

def wif_to_private_key(wif):
    """Convert WIF format to private key bytes"""
    decoded = base58_decode(wif)
    # Remove version byte (0x80) and checksum (last 4 bytes)
    private_key_bytes = decoded[1:-4]
    return private_key_bytes

def private_key_from_wif(wif):
    """Generate private key object from WIF"""
    private_key_bytes = wif_to_private_key(wif)
    private_key = ec.derive_private_key(
        int.from_bytes(private_key_bytes, 'big'),
        ec.SECP256K1(),
        default_backend()
    )
    return private_key

def stm_to_public_key(stm_key):
    """Convert STM format public key to cryptography public key object"""
    # Remove STM prefix and decode
    key_data = base58_decode(stm_key[3:])
    # Remove checksum (last 4 bytes)
    point_data = key_data[:-4]
    
    # Parse compressed public key (33 bytes: 0x02/0x03 + 32 bytes)
    if len(point_data) == 33:
        x = int.from_bytes(point_data[1:], 'big')
        y_is_even = point_data[0] == 0x02
        
        # Calculate y coordinate
        p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
        y_squared = (pow(x, 3, p) + 7) % p
        y = pow(y_squared, (p + 1) // 4, p)
        
        if y % 2 != (0 if y_is_even else 1):
            y = p - y
        
        public_key = ec.EllipticCurvePublicKey.from_encoded_point(
            ec.SECP256K1(),
            b'\x04' + x.to_bytes(32, 'big') + y.to_bytes(32, 'big')
        )
        return public_key
    
    raise ValueError("Invalid public key format")

def generate_shared_secret(private_key, public_key):
    """Generate shared secret using ECDH"""
    if isinstance(private_key, str):
        private_key = public_key_from_wif(private_key).private_key()
    
    shared_point = private_key.exchange(ec.ECDH(), public_key)
    return shared_point

def encrypt_message(message, recipient_public_key, sender_private_key):
    """Encrypt message using Hive memo format"""
    # Generate shared secret
    if isinstance(sender_private_key, str):
        sender_private_key_obj = private_key_from_wif(sender_private_key)
    else:
        sender_private_key_obj = sender_private_key
    
    if isinstance(recipient_public_key, str):
        recipient_public_key_obj = stm_to_public_key(recipient_public_key)
    else:
        recipient_public_key_obj = recipient_public_key
    
    shared_secret = generate_shared_secret(sender_private_key_obj, recipient_public_key_obj)
    
    # Generate nonce
    nonce = os.urandom(8)
    
    # Derive encryption key
    key_material = shared_secret + nonce
    encryption_key = hashlib.sha512(key_material).digest()[:32]
    
    # Encrypt message
    iv = os.urandom(16)
    cipher = Cipher(algorithms.AES(encryption_key), modes.CBC(iv), backend=default_backend())
    encryptor = cipher.encryptor()
    
    # Pad message to 16-byte boundary
    padded_message = message.encode('utf-8')
    padding_length = 16 - (len(padded_message) % 16)
    padded_message += bytes([padding_length] * padding_length)
    
    ciphertext = encryptor.update(padded_message) + encryptor.finalize()
    
    # Get sender's public key for the memo
    sender_public_key = sender_private_key_obj.public_key()
    sender_public_key_bytes = sender_public_key.public_numbers().x.to_bytes(32, 'big')
    
    # Create memo format: nonce + sender_pubkey + iv + ciphertext
    memo_data = nonce + sender_public_key_bytes + iv + ciphertext
    
    # Encode as base64 and add # prefix
    encoded_memo = '#' + base64.b64encode(memo_data).decode('utf-8')
    
    return encoded_memo

def decrypt_message(encrypted_memo, recipient_private_key):
    """Decrypt message using Hive memo format"""
    # Remove # prefix and decode base64
    if not encrypted_memo.startswith('#'):
        raise ValueError("Invalid memo format - should start with #")
    
    memo_data = base64.b64decode(encrypted_memo[1:])
    
    # Parse memo components
    nonce = memo_data[:8]
    sender_pubkey_x = memo_data[8:40]
    iv = memo_data[40:56]
    ciphertext = memo_data[56:]
    
    # Reconstruct sender's public key (assuming even y coordinate)
    sender_x = int.from_bytes(sender_pubkey_x, 'big')
    p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
    y_squared = (pow(sender_x, 3, p) + 7) % p
    y = pow(y_squared, (p + 1) // 4, p)
    
    # Try even y coordinate first
    try:
        if y % 2 != 0:
            y = p - y
        
        sender_public_key = ec.EllipticCurvePublicKey.from_encoded_point(
            ec.SECP256K1(),
            b'\x04' + sender_x.to_bytes(32, 'big') + y.to_bytes(32, 'big')
        )
    except:
        # Try odd y coordinate
        y = p - y
        sender_public_key = ec.EllipticCurvePublicKey.from_encoded_point(
            ec.SECP256K1(),
            b'\x04' + sender_x.to_bytes(32, 'big') + y.to_bytes(32, 'big')
        )
    
    # Convert sender's public key to STM format for display
    sender_public_numbers = sender_public_key.public_numbers()
    x = sender_public_numbers.x.to_bytes(32, 'big')
    y_is_even = sender_public_numbers.y % 2 == 0
    compressed_pubkey = (b'\x02' if y_is_even else b'\x03') + x
    checksum = hashlib.sha256(compressed_pubkey).digest()[:4]
    sender_stm_pubkey = 'STM' + base58_encode(compressed_pubkey + checksum)
    
    # Generate shared secret
    if isinstance(recipient_private_key, str):
        recipient_private_key_obj = private_key_from_wif(recipient_private_key)
    else:
        recipient_private_key_obj = recipient_private_key
    
    shared_secret = generate_shared_secret(recipient_private_key_obj, sender_public_key)
    
    # Derive decryption key
    key_material = shared_secret + nonce
    decryption_key = hashlib.sha512(key_material).digest()[:32]
    
    # Decrypt message
    cipher = Cipher(algorithms.AES(decryption_key), modes.CBC(iv), backend=default_backend())
    decryptor = cipher.decryptor()
    
    padded_message = decryptor.update(ciphertext) + decryptor.finalize()
    
    # Remove padding
    padding_length = padded_message[-1]
    message = padded_message[:-padding_length].decode('utf-8')
    
    return message, sender_stm_pubkey

def generate_hive_keys():
    """Generate a new Hive-compatible key pair"""
    private_key = ec.generate_private_key(ec.SECP256K1(), default_backend())
    private_key_bytes = private_key.private_numbers().private_value.to_bytes(32, 'big')
    
    # Create WIF format
    extended_key = b'\x80' + private_key_bytes
    checksum = hashlib.sha256(hashlib.sha256(extended_key).digest()).digest()[:4]
    wif = base58_encode(extended_key + checksum)
    
    # Create STM format public key
    public_key = private_key.public_key()
    public_key_point = public_key.public_numbers()
    
    # Compress public key
    x = public_key_point.x.to_bytes(32, 'big')
    y_is_even = public_key_point.y % 2 == 0
    compressed_pubkey = (b'\x02' if y_is_even else b'\x03') + x
    
    # Add checksum for STM format
    checksum = hashlib.sha256(compressed_pubkey).digest()[:4]
    stm_pubkey = 'STM' + base58_encode(compressed_pubkey + checksum)
    
    return wif, stm_pubkey

def main():
    print("=== Offline Hive Memo Tool ===")
    
    while True:
        print("\nOptions:")
        print("1. Generate new key pair")
        print("2. Encrypt message")
        print("3. Decrypt message")
        print("4. Exit")
        
        choice = input("\nEnter choice (1-4): ").strip()
        
        if choice == '1':
            print("\nGenerating new Hive key pair...")
            private_key, public_key = generate_hive_keys()
            print(f"Private Key (WIF): {private_key}")
            print(f"Public Key (STM): {public_key}")
            
        elif choice == '2':
            try:
                message = input("\nEnter message to encrypt: ")
                recipient_pubkey = input("Enter recipient's public key (STM...): ").strip()
                sender_privkey = input("Enter your private key (5...): ").strip()
                filename = input("Enter output filename: ").strip()
                
                encrypted = encrypt_message(message, recipient_pubkey, sender_privkey)
                
                with open(filename, 'w') as f:
                    f.write(encrypted)
                
                print(f"\nMessage encrypted and saved to {filename}")
                print(f"Encrypted memo: {encrypted}")
                
            except Exception as e:
                print(f"Encryption error: {e}")
        
        elif choice == '3':
            try:
                filename = input("\nEnter encrypted file path: ").strip()
                private_key = input("Enter your private key (5...): ").strip()
                
                with open(filename, 'r') as f:
                    encrypted_memo = f.read().strip()
                
                decrypted_message, sender_pubkey = decrypt_message(encrypted_memo, private_key)
                print(f"\nDecrypted message: {decrypted_message}")
                print(f"Sender's public key: {sender_pubkey}")
                print(f"\n(You can reply by encrypting a message to: {sender_pubkey})")
                
            except Exception as e:
                print(f"Decryption error: {e}")
        
        elif choice == '4':
            print("Goodbye!")
            break
        
        else:
            print("Invalid choice. Please try again.")

if __name__ == "__main__":
    main()
  • OutPut:
=== Offline Hive Memo Tool ===

Options:
1. Generate new key pair
2. Encrypt message
3. Decrypt message
4. Exit

Enter choice (1-4): 

==Example Use ==

  • In this example 2 key pairs where created, to send and receive
(hive_beem_env) ubuntu@keys:~$ python3 crypt.py 
=== Offline Hive Memo Tool ===

Options:
1. Generate new key pair
2. Encrypt message
3. Decrypt message
4. Exit

Enter choice (1-4): 1

Generating new Hive key pair...
Private Key (WIF): 5KR1behsskwnusyUMGZMNVdemVFUvoJTLkj4M7UhN3jpv7G5yK8
Public Key (STM): STM6VsVfhBdhs5pcX7CoTeoyWtxin2SPPFyMPcenbRQjRJLcjwaBK

Options:
1. Generate new key pair
2. Encrypt message
3. Decrypt message
4. Exit

Enter choice (1-4): 1

Generating new Hive key pair...
Private Key (WIF): 5JZqP9VBVL5tTqHCuAjPd4WuJDv84e56fA48wc6CnKtMq6Z4cVe
Public Key (STM): STM5aV2anXQonjiBtjSiF8K8g336aDviXQq9j2441hTgVdm3qD3BU

Options:
1. Generate new key pair
2. Encrypt message
3. Decrypt message
4. Exit

Enter choice (1-4): 4
Goodbye!
(hive_beem_env) ubuntu@keys:~$ python3 crypt.py 
=== Offline Hive Memo Tool ===

Options:
1. Generate new key pair
2. Encrypt message
3. Decrypt message
4. Exit

Enter choice (1-4): 2

Enter message to encrypt: test message
Enter recipient's public key (STM...): STM5aV2anXQonjiBtjSiF8K8g336aDviXQq9j2441hTgVdm3qD3BU
Enter your private key (5...): 5KR1behsskwnusyUMGZMNVdemVFUvoJTLkj4M7UhN3jpv7G5yK8
Enter output filename: test.msg

Message encrypted and saved to test.msg
Encrypted memo: #ha05gnlySH/UCOEW78Gx6FFlqYSvylks99XfTfyzp7Pq1Zx/o0YDrvCBZIM9v6wiccjyNOkcwT45rzI5MXBIyGsoHDN6lC4D

Options:
1. Generate new key pair
2. Encrypt message
3. Decrypt message
4. Exit

Enter choice (1-4): 3

Enter encrypted file path: test.msg
Enter your private key (5...): 5JZqP9VBVL5tTqHCuAjPd4WuJDv84e56fA48wc6CnKtMq6Z4cVe

Decrypted message: test message
Sender's public key: STM6VsVfhBdhs5pcX7CoTeoyWtxin2SPPFyMPcenbRQjRJLcjwaBK

(You can reply by encrypting a message to: STM6VsVfhBdhs5pcX7CoTeoyWtxin2SPPFyMPcenbRQjRJLcjwaBK)

Options:
1. Generate new key pair
2. Encrypt message
3. Decrypt message
4. Exit

Enter choice (1-4): 2

Enter message to encrypt: so not doing much then?
Enter recipient's public key (STM...): STM6VsVfhBdhs5pcX7CoTeoyWtxin2SPPFyMPcenbRQjRJLcjwaBK
Enter your private key (5...): 5JZqP9VBVL5tTqHCuAjPd4WuJDv84e56fA48wc6CnKtMq6Z4cVe
Enter output filename: test2.msg

Message encrypted and saved to test2.msg
Encrypted memo: #f0SOHdMtUA5az+j9s3iwVbThCKvHCy1j3mLZTS45ymAYtAAhVv+6g9Bll1V0unDOIksVC2mdTduPor9IKM4DygfrQz1KSxsf3uC/za+Jxd6BIv4N5l5L1g==

Options:
1. Generate new key pair
2. Encrypt message
3. Decrypt message
4. Exit

Enter choice (1-4): 3

Enter encrypted file path: test2.msg
Enter your private key (5...): 5KR1behsskwnusyUMGZMNVdemVFUvoJTLkj4M7UhN3jpv7G5yK8

Decrypted message: so not doing much then?
Sender's public key: STM5aV2anXQonjiBtjSiF8K8g336aDviXQq9j2441hTgVdm3qD3BU

(You can reply by encrypting a message to: STM5aV2anXQonjiBtjSiF8K8g336aDviXQq9j2441hTgVdm3qD3BU)

Options:
1. Generate new key pair
2. Encrypt message
3. Decrypt message
4. Exit

Enter choice (1-4): 

== Example of Script use 2 ==

  • Below is some test keys generated for this demo
  • This time it did not give same public key from signing private key - but still worked.
RECEIVING
Key Pair 1:
Generating new Hive key pair...
Private Key (WIF): 5JxAbGJ7XFcpAY3rW3pmzGapEkyrSf9wz14Lar3ZMvxUpA14uaR
Public Key (STM): STM5KtMabHkqWdwBJavkFRpHd4UkYSMQJiofW3vicw68t6y53YYpF

SENDING
Key Pair 2:
Generating new Hive key pair...
Private Key (WIF): 5HvNXDZYh5QFFikGnwv8cs7u2nF6eiUTKPScBfdy4QzvJWqKCLo
Public Key (STM): STM6a9JCL7LXKpKDYjaYiwFtdqydkh9aha5YrH7NoKm3wdkw4eUFg

=== Encryption and Decryption Example 2 ===

(hive_beem_env) ubuntu@keys:~$ python3 crypt.py 
=== Offline Hive Memo Tool ===

Options:
1. Generate new key pair
2. Encrypt message
3. Decrypt message
4. Exit

Enter choice (1-4): 2

Enter message to encrypt: yo whats up :) does this make sense to you.
Enter recipient's public key (STM...): STM5KtMabHkqWdwBJavkFRpHd4UkYSMQJiofW3vicw68t6y53YYpF
Enter your private key (5...): 5HvNXDZYh5QFFikGnwv8cs7u2nF6eiUTKPScBfdy4QzvJWqKCLo
Enter output filename: newtest.msg

Message encrypted and saved to newtest.msg
Encrypted memo: #syhpjq7Bae/dvF3/yeUdtk9leWooSemGYGSgFlxTXmDl9t4rnDoT1/YHo7HMnf+ZM3FPmMZmNZFeSUUAeWcP+m3g9L/7wvdWP0XXtTqEgoZZOl1BAqJTHIdHxQKDQ1++jLx2YZEkxKo=

Options:
1. Generate new key pair
2. Encrypt message
3. Decrypt message
4. Exit

Enter choice (1-4): 3

Enter encrypted file path: newtest.msg
Enter your private key (5...): 5JxAbGJ7XFcpAY3rW3pmzGapEkyrSf9wz14Lar3ZMvxUpA14uaR

Decrypted message: yo whats up :) does this make sense to you.
Sender's public key: STM6a9JCL7LXKpKDYjaYiwFtdqydkh9aha5YrH7NoKm3wdkuxXxXg

(You can reply by encrypting a message to: STM6a9JCL7LXKpKDYjaYiwFtdqydkh9aha5YrH7NoKm3wdkuxXxXg)

Options:
1. Generate new key pair
2. Encrypt message
3. Decrypt message
4. Exit

Enter choice (1-4): 2

Enter message to encrypt: reply to sender
Enter recipient's public key (STM...): STM6a9JCL7LXKpKDYjaYiwFtdqydkh9aha5YrH7NoKm3wdkuxXxXg
Enter your private key (5...): 5JxAbGJ7XFcpAY3rW3pmzGapEkyrSf9wz14Lar3ZMvxUpA14uaR
Enter output filename: reply.msg

Message encrypted and saved to reply.msg
Encrypted memo: #x06sC2YmlHQ5qoKbtCsiibPc7YGejYH1mqxsX01cMQ0UhSo6gKLCWM65AjL8dl80Jdhb6iuJlrlCrlZgdZkO8at0vBGTtLin

Options:
1. Generate new key pair
2. Encrypt message
3. Decrypt message
4. Exit

Enter choice (1-4): 3

Enter encrypted file path: reply.msg
Enter your private key (5...): 5HvNXDZYh5QFFikGnwv8cs7u2nF6eiUTKPScBfdy4QzvJWqKCLo

Decrypted message: reply to sender
Sender's public key: STM5KtMabHkqWdwBJavkFRpHd4UkYSMQJiofW3vicw68t6y7eTR76

(You can reply by encrypting a message to: STM5KtMabHkqWdwBJavkFRpHd4UkYSMQJiofW3vicw68t6y7eTR76)

Options:
1. Generate new key pair
2. Encrypt message
3. Decrypt message
4. Exit

Enter choice (1-4): 

Posted Using INLEO

Comments (1)

noblemage's avatar @noblemage 6/16/2025

NOTE: Beem is installed in Virtual Environment to avoid error: externally-managed-environment