Samsung NVMe firmware updates require fumagician — an x86-64 Linux binary that cannot run on ARM64 systems. This guide documents how to apply official Samsung firmware on ARM64 using standard nvme-cli tools.
Samsung distributes firmware as bootable ISOs containing:
fumagician— x86-64 ELF binary (update tool)*.enc— AES-256-ECB encrypted firmware files
The encryption prevents direct use of firmware files with nvme fw-download.
- Extract the AES key from
fumagicianbinary - Decrypt the transport layer encryption
- Upload firmware using standard NVMe commands
The SSD handles device-specific decryption internally — we only need to remove the transport layer.
- Linux system (any architecture) with Python 3
nvme-clipackage on target systempycryptodomePython library- Samsung firmware ISO
# Download firmware ISO from Samsung
# Example: Samsung_SSD_990_EVO_1B2QKXJ7.iso
mkdir -p /tmp/samsung_fw && cd /tmp/samsung_fw
# Mount ISO (or extract with 7z)
sudo mount -o loop Samsung_SSD_990_EVO_*.iso /mnt
# Extract initrd (contains firmware)
gzip -dc /mnt/initrd | cpio -idmv
# Files are in root/fumagician/
ls root/fumagician/
# *.enc files and fumagician binaryThe AES-256 key is stored as base64 in the fumagician binary:
# Find base64-encoded 32-byte key
strings root/fumagician/fumagician | grep -E '^[A-Za-z0-9+/]{43}=$'
# Output will be a 44-character base64 string
# Decode to get 32-byte (256-bit) AES keyKeys vary between Samsung model generations. Extract from the specific fumagician version for your SSD model.
#!/usr/bin/env python3
"""
Samsung NVMe Firmware Decryption Tool
Decrypts official Samsung firmware for use with nvme-cli on ARM64/RISC-V.
Only works with official Samsung firmware - cannot be used for modification.
Usage: python3 decrypt_samsung.py <key_base64> <firmware.enc>
"""
import sys
import base64
import zipfile
from Crypto.Cipher import AES
def decrypt_aes256_ecb(data: bytes, key: bytes) -> bytes:
"""Decrypt AES-256-ECB encrypted data."""
cipher = AES.new(key, AES.MODE_ECB)
return cipher.decrypt(data)
def main():
if len(sys.argv) != 3:
print(f"Usage: {sys.argv[0]} <key_base64> <firmware.enc>")
print(" key_base64: Base64-encoded AES-256 key from fumagician")
print(" firmware.enc: Encrypted firmware file (e.g., 1B2QKXJ7.enc)")
sys.exit(1)
key_b64 = sys.argv[1]
enc_file = sys.argv[2]
# Decode key
key = base64.b64decode(key_b64)
if len(key) != 32:
print(f"Error: Key must be 32 bytes (got {len(key)})")
sys.exit(1)
# Read and decrypt outer layer
with open(enc_file, 'rb') as f:
encrypted = f.read()
decrypted = decrypt_aes256_ecb(encrypted, key)
# Verify header magic
header = decrypted[:32]
magic = decrypt_aes256_ecb(header, key) if header == encrypted[:32] else header
# Skip 32-byte fumagician header, extract ZIP
zip_data = decrypted[32:]
zip_path = enc_file.replace('.enc', '.zip')
with open(zip_path, 'wb') as f:
f.write(zip_data)
# Extract inner .enc from ZIP
with zipfile.ZipFile(zip_path, 'r') as zf:
inner_name = [n for n in zf.namelist() if n.endswith('.enc')][0]
zf.extract(inner_name)
print(f"Extracted: {inner_name}")
# Decrypt inner layer
with open(inner_name, 'rb') as f:
inner_encrypted = f.read()
inner_decrypted = decrypt_aes256_ecb(inner_encrypted, key)
# Save final payload (skip 32-byte header)
output_name = 'firmware_payload.bin'
with open(output_name, 'wb') as f:
f.write(inner_decrypted[32:])
print(f"Firmware ready: {output_name} ({len(inner_decrypted) - 32} bytes)")
print(f"\nNext steps:")
print(f" scp {output_name} user@target:/tmp/")
print(f" ssh user@target")
print(f" sudo nvme fw-download /dev/nvme0 --fw=/tmp/{output_name} --xfer=32768")
print(f" sudo nvme fw-commit /dev/nvme0 --slot=2 --action=1")
print(f" sudo reboot")
if __name__ == '__main__':
main()# Check current firmware
sudo nvme fw-log /dev/nvme0
sudo nvme id-ctrl /dev/nvme0 | grep -E '^(mn|fr|sn)'
# Upload firmware (32KB chunks)
sudo nvme fw-download /dev/nvme0 --fw=/tmp/firmware_payload.bin --xfer=32768
# Commit to slot 2 (slot 1 is typically read-only)
sudo nvme fw-commit /dev/nvme0 --slot=2 --action=1
# Verify new firmware is staged
sudo nvme fw-log /dev/nvme0
# Should show new version in frs2
# Reboot to activate
sudo reboot
# Verify after reboot
sudo nvme id-ctrl /dev/nvme0 | grep fr- Hardware: Raspberry Pi 5 (ARM64)
- OS: Debian 12 (Bookworm)
- SSD: Samsung 990 EVO 1TB
- Update: 0B2QKXJ7 → 1B2QKXJ7
- Result: Success
- Official firmware only: This method applies unmodified Samsung firmware
- Backup first: Create backups before firmware updates
- Don't interrupt: Power loss during update may brick the SSD
- Slot 1 preserved: Factory firmware remains in read-only slot 1
- First boot delay: Initial boot after update may take 1-2 minutes
Samsung uses two-layer encryption:
-
Transport layer (AES-256-ECB): Protects firmware during distribution. Key is in
fumagicianbinary. -
Device layer: Protects against firmware modification. Key is burned into SSD ROM.
We decrypt layer 1, then send the payload via standard NVMe commands. The SSD internally handles layer 2 using its ROM key.
This is why the method only works with official Samsung firmware — modified firmware would fail the SSD's internal signature verification.
This documentation is provided for interoperability purposes under DMCA §1201(f). It enables use of official Samsung firmware on platforms not supported by Samsung's tools. No Samsung intellectual property is distributed. Users must obtain firmware from official Samsung sources.
- NVMe Specification — Firmware Download/Commit commands
- nvme-cli — Linux NVMe management tool
- Samsung SSD Firmware — Official firmware downloads