=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* Will also create a text file container user keys in same directory script run:Public Keys for @completenoobs:
Posting: STM52HfmA7gmnAjq8eQbAPgTxijsoVwm3T439dgeVqjC4baXUyJSV
Active: STM4zGfF1K9TbMoCcE2eLtTwrBwVybEhgu5yfBv99nhR6kjf5Hv97
Owner: STM8jjM8CowxH6ttosaUXCK5NufZQMga18KRD6vq1LrmTH4u4poQW
Memo: STM5u4bQRcCFfbGgg29TabvAmYsMdM6eGK8sj7sJDWPPpz6SwAums
Saved keys to completenoobs_public_keys.txt
=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:
- Generate new key pair
- Encrypt message
- Decrypt message
- 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:
- Generate new key pair
- Encrypt message
- Decrypt message
- Exit
Enter choice (1-4): 1
Generating new Hive key pair...
Private Key (WIF): 5KR1behsskwnusyUMGZMNVdemVFUvoJTLkj4M7UhN3jpv7G5yK8
Public Key (STM): STM6VsVfhBdhs5pcX7CoTeoyWtxin2SPPFyMPcenbRQjRJLcjwaBKOptions:
- Generate new key pair
- Encrypt message
- Decrypt message
- Exit
Enter choice (1-4): 1
Generating new Hive key pair...
Private Key (WIF): 5JZqP9VBVL5tTqHCuAjPd4WuJDv84e56fA48wc6CnKtMq6Z4cVe
Public Key (STM): STM5aV2anXQonjiBtjSiF8K8g336aDviXQq9j2441hTgVdm3qD3BUOptions:
- Generate new key pair
- Encrypt message
- Decrypt message
- Exit
Enter choice (1-4): 4
Goodbye!
(hive_beem_env) ubuntu@keys:~$ python3 crypt.py
=== Offline Hive Memo Tool ===Options:
- Generate new key pair
- Encrypt message
- Decrypt message
- 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.msgMessage encrypted and saved to test.msg
Encrypted memo: #ha05gnlySH/UCOEW78Gx6FFlqYSvylks99XfTfyzp7Pq1Zx/o0YDrvCBZIM9v6wiccjyNOkcwT45rzI5MXBIyGsoHDN6lC4DOptions:
- Generate new key pair
- Encrypt message
- Decrypt message
- Exit
Enter choice (1-4): 3
Enter encrypted file path: test.msg
Enter your private key (5...): 5JZqP9VBVL5tTqHCuAjPd4WuJDv84e56fA48wc6CnKtMq6Z4cVeDecrypted message: test message
Sender's public key: STM6VsVfhBdhs5pcX7CoTeoyWtxin2SPPFyMPcenbRQjRJLcjwaBK(You can reply by encrypting a message to: STM6VsVfhBdhs5pcX7CoTeoyWtxin2SPPFyMPcenbRQjRJLcjwaBK)
Options:
- Generate new key pair
- Encrypt message
- Decrypt message
- 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.msgMessage encrypted and saved to test2.msg
Encrypted memo: #f0SOHdMtUA5az+j9s3iwVbThCKvHCy1j3mLZTS45ymAYtAAhVv+6g9Bll1V0unDOIksVC2mdTduPor9IKM4DygfrQz1KSxsf3uC/za+Jxd6BIv4N5l5L1g==Options:
- Generate new key pair
- Encrypt message
- Decrypt message
- Exit
Enter choice (1-4): 3
Enter encrypted file path: test2.msg
Enter your private key (5...): 5KR1behsskwnusyUMGZMNVdemVFUvoJTLkj4M7UhN3jpv7G5yK8Decrypted message: so not doing much then?
Sender's public key: STM5aV2anXQonjiBtjSiF8K8g336aDviXQq9j2441hTgVdm3qD3BU(You can reply by encrypting a message to: STM5aV2anXQonjiBtjSiF8K8g336aDviXQq9j2441hTgVdm3qD3BU)
Options:
- Generate new key pair
- Encrypt message
- Decrypt message
- 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): STM5KtMabHkqWdwBJavkFRpHd4UkYSMQJiofW3vicw68t6y53YYpFSENDING
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:
- Generate new key pair
- Encrypt message
- Decrypt message
- 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.msgMessage encrypted and saved to newtest.msg
Encrypted memo: #syhpjq7Bae/dvF3/yeUdtk9leWooSemGYGSgFlxTXmDl9t4rnDoT1/YHo7HMnf+ZM3FPmMZmNZFeSUUAeWcP+m3g9L/7wvdWP0XXtTqEgoZZOl1BAqJTHIdHxQKDQ1++jLx2YZEkxKo=Options:
- Generate new key pair
- Encrypt message
- Decrypt message
- Exit
Enter choice (1-4): 3
Enter encrypted file path: newtest.msg
Enter your private key (5...): 5JxAbGJ7XFcpAY3rW3pmzGapEkyrSf9wz14Lar3ZMvxUpA14uaRDecrypted 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:
- Generate new key pair
- Encrypt message
- Decrypt message
- 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.msgMessage encrypted and saved to reply.msg
Encrypted memo: #x06sC2YmlHQ5qoKbtCsiibPc7YGejYH1mqxsX01cMQ0UhSo6gKLCWM65AjL8dl80Jdhb6iuJlrlCrlZgdZkO8at0vBGTtLinOptions:
- Generate new key pair
- Encrypt message
- Decrypt message
- Exit
Enter choice (1-4): 3
Enter encrypted file path: reply.msg
Enter your private key (5...): 5HvNXDZYh5QFFikGnwv8cs7u2nF6eiUTKPScBfdy4QzvJWqKCLoDecrypted message: reply to sender
Sender's public key: STM5KtMabHkqWdwBJavkFRpHd4UkYSMQJiofW3vicw68t6y7eTR76(You can reply by encrypting a message to: STM5KtMabHkqWdwBJavkFRpHd4UkYSMQJiofW3vicw68t6y7eTR76)
Options:
- Generate new key pair
- Encrypt message
- Decrypt message
- Exit
Enter choice (1-4):
Posted Using INLEO
NOTE: Beem is installed in Virtual Environment to avoid
error: externally-managed-environment