technical ยท 6 min read

BOA QR Codes: Encrypted Receipts and How Decryption Works

Bank of Abyssinia QR codes use AES-256-CBC encryption, not URLs like CBE. Here's how the encryption works, what's inside the payload, and how cheki decrypts it for instant inter-bank verification.

Bank of Abyssinia takes a different approach to receipt QR codes than CBE. Instead of encoding a URL that points to a server-side receipt, BOA encrypts the full transaction data directly into the QR code. This means the QR code itself IS the receipt, not just a link to one.

โ„น

Two approaches, same goal

CBE's QR codes encode a short URL (mbreciept.cbe.com.et/{id}) that redirects to server-side data. BOA's QR codes encode the transaction data itself, encrypted with AES-256-CBC. Both work, but they have different security trade-offs.

What the QR code contains

When you scan a BOA receipt QR code with a standard QR reader, you get a base64-encoded string like this:

code
3cHRaxVjn/pySpNXEHQE61JOQ2poZRMwnDHwMiX7YO9UVtJZT/ndmwHEzWkJoloEf4dIQIzJf5zmvbBo5qHTdm/23nc6NRzTSfxEjIHa7Ju4Ti+xydrVn8qF+9/OPAF5LIfMEvxFqZ6wlKMvSN/jrQ==

This is not a URL. It's a base64-encoded ciphertext. When decoded and decrypted, it reveals a comma-separated string with 7 fields:

code
senderAccount,senderName,amount,reference,date,receiverAccount,receiverName

For example, a real decrypted payload looks like:

code
1000370251685,EYOUEL ARAGAW HAILE,1107.59,FT252003JZPP,19/07/2025,1000370251685,MOHAMMED ABDULWASI RESHID

How the encryption works

BOA uses the CryptoJS library for encryption. CryptoJS is a popular JavaScript encryption library. The encryption parameters are hardcoded in BOA's receipt web app, which is served from cs.bankofabyssinia.com/slip/assets/index-*.js. Here are the exact parameters:

ParameterValuePurpose
AlgorithmAES-256-CBCSymmetric encryption with 256-bit key and CBC mode
PasswordELqVy2g4pGWLUIKSa+1ijwpPy6eDxBFBLBPrJ24v/IA=Base64-encoded passphrase for key derivation
SaltsaltPBKDF2 salt (static string)
IV123456789012345616-byte initialization vector (static)
Iterations10000PBKDF2 key derivation iterations
Key length32 bytes (256 bits)Derived key length for AES-256
Hash functionSHA1PBKDF2 hash function for key derivation

The decryption process works in 3 steps:

  1. Base64 decode the QR payload to get the raw ciphertext bytes
  2. Derive the AES key using PBKDF2 with the password, salt, 10000 iterations, SHA1, and 32-byte key length
  3. Decrypt the ciphertext using AES-256-CBC with the derived key and the static IV

The result is a UTF-8 string in CSV format with 7 comma-separated fields.

Why the key is public

BOA's receipt viewer is a client-side web app. When someone shares a receipt link, the recipient opens it in their browser and the JavaScript app decrypts the QR data locally to display the receipt. This means the decryption key must be embedded in the JavaScript that runs in the browser. Anyone who inspects the JS bundle can extract it.

โš 

Security implication

Because the key is public, anyone can create a valid-looking BOA QR payload from scratch. You could encrypt arbitrary transaction data with the same key and produce a QR code that cheki (or BOA's own app) would decrypt as 'verified.' This is a fundamental limitation of BOA's design, not a cheki issue. The same applies to any verification tool that decrypts BOA QR codes.

โœ•

Forged QR risk for inter-bank

For normal BOA-to-BOA transfers, you can cross-check the QR data against BOA's JSON API. But for inter-bank transfers (BOA to CBE, Dashen, etc.), the API returns 'Invalid reference number,' so the QR code is the ONLY proof. Since the key is public, a forged QR code cannot be distinguished from a real one. Always verify inter-bank transfers with additional confirmation (bank statement, SMS alert, branch confirmation) for high-value transactions.

Inter-bank vs intra-bank: two verification paths

BOA has two distinct verification paths depending on whether the transfer stays within BOA or goes to another bank:

Transfer typeAPI lookupQR decryptionRecommended method
BOA to BOA (intra-bank)Works (JSON API)WorksAPI lookup (can cross-check)
BOA to CBE/Dashen/other (inter-bank)Fails ('Invalid reference number')WorksQR decryption (only option)

When a customer sends money from BOA to CBE, the transaction reference starts with 'FT' (e.g., FT252003JZPP). BOA's online slip API at cs.bankofabyssinia.com does not recognize these references because the transfer left BOA's system. The QR code, however, was generated at the time of transfer and contains all the details.

How cheki handles BOA QR codes

cheki's unified scanner auto-detects BOA QR payloads. When you scan a QR code or upload a receipt image, cheki checks whether the decoded string matches BOA's encrypted format (base64 string with correct AES block alignment). If it does, cheki decrypts it server-side via the /api/verify endpoint and returns the parsed transaction data.

1

Scan or upload

Open cheki, tap the camera icon to scan the QR code on the receipt, or upload a screenshot/photo of the receipt. cheki's multi-scale QR detector finds QR codes even in full receipt screenshots.

2

Auto-detection

cheki checks if the decoded string is a BOA encrypted payload (base64, correct length, AES block-aligned). If yes, it routes to BOA QR decryption. If it's a URL, it routes to URL verification. If it's a CBE reference, it routes to CBE.

3

Server-side decryption

The encrypted payload is sent to cheki's /api/verify endpoint, which decrypts it server-side using Node.js crypto. This prevents client-side tampering where someone could modify the decryption logic in the browser.

4

Instant result

Decryption takes ~8ms server-side. The full transaction data (sender, receiver, amount, date, reference) appears in under 500ms including network round-trip.

๐Ÿ’ก

No account number needed for QR

Unlike BOA's JSON API which requires the last 5 digits of the receiving account, QR-based verification needs no additional information. The QR code contains everything. Just scan and verify.

BOA QR vs CBE QR: a comparison

FeatureBOA QR codeCBE QR code
EncodingAES-256-CBC encrypted payloadPlain URL (mbreciept.cbe.com.et/{id})
Contains transaction dataYes (encrypted in QR)No (data fetched from server)
Requires server callNo (can decrypt locally)Yes (must call CBE API)
Account number neededNoNo (new system) / Yes (old system)
Inter-bank supportYes (QR is the only option)N/A (CBE receipts always work)
Forgery riskHigh (key is public, can forge QR)Low (server validates ID)
Speed~500ms (decrypt + network)~1-2s (API call + JSON parse)
Offline verificationPossible (decrypt locally)Not possible (needs API)

Verifying BOA QR codes via API

Send the QR payload in the qrData field. No reference or account number is needed:

bash
curl -X POST https://chekiapp.vercel.app/api/verify \
  -H "Content-Type: application/json" \
  -d '{"bank":"boa","qrData":"3cHRaxVjn/pySpNXEHQE61JOQ2poZRMwnDHwMiX7YO9UVtJZT/ndmwHEzWkJoloEf4dIQIzJf5zmvbBo5qHTdm/23nc6NRzTSfxEjIHa7Ju4Ti+xydrVn8qF+9/OPAF5LIfMEvxFqZ6wlKMvSN/jrQ=="}'

Response:

json
{
  "verified": true,
  "bank": "Bank of Abyssinia",
  "senderName": "EYOUEL ARAGAW HAILE",
  "senderAccount": "1000370251685",
  "receiverName": "MOHAMMED ABDULWASI RESHID",
  "receiverAccount": "1000370251685",
  "amount": 1107.59,
  "currency": "ETB",
  "date": "19/07/2025",
  "reference": "FT252003JZPP"
}

Extracting the key from BOA's web app

The decryption key and parameters are in BOA's receipt viewer JavaScript bundle. Here's where to find them:

code
# The receipt viewer is at:
# https://cs.bankofabyssinia.com/slip/{receipt-id}
#
# The JavaScript bundle is loaded from:
# https://cs.bankofabyssinia.com/slip/assets/index-{hash}.js
#
# Search the bundle for CryptoJS.AES.decrypt to find:
#   - The password string
#   - The salt, IV, iterations, and hash function
# All are passed as literals to CryptoJS.AES.decrypt()
โš 

Key may change

If BOA updates their receipt viewer JavaScript, the password or parameters could change. cheki monitors this and updates the parser when needed. If you're self-hosting, check for key rotation after BOA app updates.

Frequently asked questions

Edit this article on GitHubReport an issue โ†’

Continue reading