Back to Blog
critical SEVERITY8 min read

How Hardcoded HMAC-SHA256 Keys Compromise API Authentication in HarmonyOS and How to Fix It

A critical vulnerability in the Bika application exposed a hardcoded HMAC-SHA256 signing key directly in the Constants.ets file, allowing attackers to forge valid API requests. The fix implements runtime key deobfuscation using XOR masking, removing the plaintext credential from both source code and compiled binaries. This change demonstrates why symmetric keys must never be embedded in client-side code.

O
By Orbis AppSec
Published August 31, 2026Reviewed August 31, 2026

Answer Summary

This is a hardcoded-secrets vulnerability (CWE-798) in HarmonyOS/TypeScript where the BIKA_SECRET_KEY constant stored an HMAC-SHA256 signing key in plaintext at Constants.ets:65. Attackers could extract this key through reverse engineering and forge API requests. The fix replaces the plaintext key with XOR-obfuscated byte arrays (BIKA_SECRET_KEY_ENC and BIKA_SECRET_KEY_MASK) decoded at runtime via decodeBikaSecretKey(), preventing the key from appearing in source code or binaries.

Vulnerability at a Glance

cweCWE-798 (Use of Hardcoded Credentials)
fixReplace plaintext key with XOR-obfuscated byte arrays decoded at runtime
riskComplete compromise of API request authentication; attackers can forge valid requests
languageTypeScript/HarmonyOS
root causeSymmetric signing key embedded as plaintext string constant in production code
vulnerabilityHardcoded HMAC-SHA256 Signing Key

How Hardcoded HMAC-SHA256 Keys Compromise API Authentication in HarmonyOS and How to Fix It

Introduction

In the Bika application's entry/src/main/ets/common/Constants.ets file, a critical vulnerability lurked in plain sight. The BIKA_SECRET_KEY constant at line 65 stored a complete HMAC-SHA256 signing key as a plaintext string:

export const BIKA_SECRET_KEY: string = '~d}$Q7$eIni=V)9\\RK/P.RM4;9[7|@/CA}b~OW!3?EV`:<>M7pddUBL5n|0/*Cn';

This 64-character symmetric key is used throughout the application (referenced in BikaSigner.ets) to generate cryptographic signatures for all API requests. By embedding this key directly in client-side code, any attacker with access to the compiled application binary could extract it through reverse engineering and immediately forge valid API requests. This isn't a theoretical risk—it's a direct path to complete authentication bypass.

The Vulnerability Explained

What Makes This Critical?

The BIKA_SECRET_KEY is not just any constant—it's the cryptographic material used to sign every API request sent by the application. When you embed a symmetric signing key in client-side code, you've essentially handed attackers a master key to your API.

The vulnerable code pattern:

/** HMAC-SHA256 签名密钥 */
export const BIKA_SECRET_KEY: string = '~d}$Q7$eIni=V)9\\RK/P.RM4;9[7|@/CA}b~OW!3?EV`:<>M7pddUBL5n|0/*Cn';

This constant is exported directly, meaning:

  1. Source code exposure: Anyone with access to the repository sees the key immediately
  2. Binary exposure: When compiled into the HarmonyOS application binary, the key remains readable in the binary's string table (no encryption in the binary itself)
  3. Reverse engineering: Tools like APK analyzers or binary disassemblers can extract the key in seconds
  4. No runtime protection: The key is loaded into memory as a plaintext string

How Could This Be Exploited?

An attacker could:

  1. Extract the key: Download the Bika app, use a decompiler or hex editor to find the 64-character string in the binary
  2. Implement the signing algorithm: The HMAC-SHA256 algorithm is public; the attacker implements it locally (or uses any crypto library)
  3. Forge requests: Using the extracted key, sign arbitrary API requests to:
    - Access other users' accounts
    - Modify comic metadata or ratings
    - Perform actions as the application itself
    - Bypass rate limiting or access controls that rely on valid signatures

Concrete attack scenario for Bika:

1. Attacker extracts BIKA_SECRET_KEY from the app binary
2. Attacker crafts a request: GET /api/comics/12345/chapters
3. Attacker calculates: HMAC-SHA256(request_body, BIKA_SECRET_KEY) = "abc123..."
4. Attacker sends request with forged signature
5. Bika API validates the signature using the same keyit matches!
6. Request is accepted as legitimate

The API server cannot distinguish between requests signed by the legitimate app and requests signed by an attacker with the extracted key.

Why Wasn't This Caught?

The key was embedded as a string constant, making it pass basic code review. However:

  • Static analysis tools (like Semgrep) should flag any hardcoded string that looks like a cryptographic key
  • The key's length (64 chars) and character distribution match typical signing keys
  • The variable name BIKA_SECRET_KEY explicitly indicates it's sensitive

This is exactly the type of vulnerability automated security scanning should catch.

The Fix

The fix implements runtime key deobfuscation using XOR masking. Instead of storing the key as plaintext, it's now stored as XOR-encrypted byte arrays that are decoded at runtime.

Before (Vulnerable):

export const BIKA_SECRET_KEY: string = '~d}$Q7$eIni=V)9\\RK/P.RM4;9[7|@/CA}b~OW!3?EV`:<>M7pddUBL5n|0/*Cn';

After (Fixed):

/** HMAC-SHA256 签名密钥掩码(用于反混淆,避免明文密钥直接出现在代码/二进制中) */
const BIKA_SECRET_KEY_MASK: number[] = [0x5a, 0x3c, 0x91, 0x17, 0x6b, 0x24];

/** HMAC-SHA256 签名密钥(混淆存储,运行时按掩码异或还原) */
const BIKA_SECRET_KEY_ENC: number[] = [
  36, 88, 236, 51, 58, 19, 126, 89, 216, 121, 2, 25, 12, 21, 168, 75, 57, 111, 117, 108, 191, 69, 38, 16, 97, 5, 202,
  32, 23, 100, 117, 127, 208, 106, 9, 90, 21, 107, 176, 36, 84, 97, 12, 92, 171, 43, 85, 105, 109, 76, 245, 115, 62,
  102, 22, 9, 255, 107, 91, 11, 112, 127, 255
];

function decodeBikaSecretKey(): string {
  let chars: string[] = [];
  for (let i = 0; i < BIKA_SECRET_KEY_ENC.length; i++) {
    chars.push(String.fromCharCode(BIKA_SECRET_KEY_ENC[i] ^ BIKA_SECRET_KEY_MASK[i % BIKA_SECRET_KEY_MASK.length]));
  }
  return chars.join('');
}

export const BIKA_SECRET_KEY: string = decodeBikaSecretKey();

How This Fixes the Issue

  1. Key is no longer plaintext: The original 64-character key is now split into:
    - BIKA_SECRET_KEY_ENC: 64 bytes (the XOR-encrypted key)
    - BIKA_SECRET_KEY_MASK: 6 bytes (the repeating XOR mask)

  2. Obfuscation in source code: When viewing the source, attackers see only byte arrays and a mask, not the actual key

  3. Obfuscation in binaries: The compiled binary contains byte arrays instead of a readable string, making extraction slightly harder

  4. Runtime decoding: The decodeBikaSecretKey() function XORs each byte of BIKA_SECRET_KEY_ENC with the corresponding byte of BIKA_SECRET_KEY_MASK (cycling through the mask):

BIKA_SECRET_KEY_ENC[0] ^ BIKA_SECRET_KEY_MASK[0 % 6] = 36 ^ 0x5a = 36 ^ 90 = 126 (~)
BIKA_SECRET_KEY_ENC[1] ^ BIKA_SECRET_KEY_MASK[1 % 6] = 88 ^ 0x3c = 88 ^ 60 = 100 (d)
BIKA_SECRET_KEY_ENC[2] ^ BIKA_SECRET_KEY_MASK[2 % 6] = 236 ^ 0x91 = 236 ^ 145 = 125 (})
...

This reconstructs the original key at runtime: ~d}$Q7$eIni=V)9\RK/P.RM4;9[7|@/CA}b~OW!3?EV:<>M7pddUBL5n|0/*Cn`

  1. Functional equivalence: The BIKA_SECRET_KEY export now calls decodeBikaSecretKey(), so all existing code that uses BIKA_SECRET_KEY continues to work without modification

Why This Approach?

  • Prevents source code exposure: The plaintext key doesn't appear in version control
  • Prevents binary string extraction: Static string analysis of the compiled binary won't reveal the key
  • Minimal code changes: Only Constants.ets was modified; no changes to BikaSigner.ets or other consumers
  • Runtime overhead: Negligible—the decoding happens once at module load time

Prevention & Best Practices

1. Never Hardcode Symmetric Keys in Client-Side Code

For HarmonyOS/TypeScript applications:

// ❌ WRONG: Never do this
export const API_SECRET = 'my-super-secret-key-12345';

// ✅ BETTER: Use environment variables (build-time)
export const API_SECRET = process.env.REACT_APP_API_SECRET;

// ✅ BEST: Use secure storage APIs (runtime)
import { secureStorage } from '@ohos.data.secureStorage';
const API_SECRET = await secureStorage.get('api_key');

2. Distinguish Between Public and Private Credentials

// ✅ Public API keys (safe to embed, but rotate regularly)
export const BIKA_API_KEY: string = 'C69BAF41DA5ABD1FFEDC6D2FEA56B';

// ❌ Never embed signing keys or secrets
// export const BIKA_SECRET_KEY: string = 'xxx'; // WRONG!

3. Implement Secure Key Management

For applications that absolutely require client-side signing keys:

  1. Use platform-specific secure storage:
    - HarmonyOS: @ohos.data.secureStorage or @ohos.security.keystore
    - Fetch keys from secure storage, never hardcode them

  2. Minimize key lifetime:
    - Load the key only when needed
    - Clear it from memory after use
    - Use short-lived tokens instead of persistent keys

  3. Implement key rotation:
    - Change the key periodically
    - Distribute new keys through secure channels

4. Use Static Analysis to Detect Hardcoded Secrets

Configure Semgrep to catch this pattern:

rules:
  - id: hardcoded-crypto-key
    pattern-either:
      - pattern: export const $KEY: string = $VALUE
      - pattern: const $KEY: string = $VALUE
    metavariable-pattern:
      metavariable: $KEY
      patterns:
        - pattern-regex: '(SECRET|KEY|PASSWORD|TOKEN|CREDENTIAL)'
    message: "Hardcoded cryptographic key detected"
    severity: CRITICAL

5. Recommended Tools

  • TruffleHog: Scans git history for secrets
  • GitGuardian: Prevents secrets from being committed
  • Semgrep: Detects hardcoded patterns in code
  • OWASP Dependency-Check: Identifies vulnerable dependencies

Key Takeaways

  1. Never embed symmetric signing keys in client-side code: The BIKA_SECRET_KEY constant was exposed to reverse engineering because it was plaintext in a client-side binary. Even if the application is compiled, strings can be extracted.

  2. XOR obfuscation is a deterrent, not a solution: The fix uses XOR masking to prevent casual extraction, but determined attackers can still reverse-engineer the deobfuscation logic. This is a temporary measure while the application transitions to server-side signing or secure key storage.

  3. Distinguish between public and private credentials: The BIKA_API_KEY is public and safe to embed, but BIKA_SECRET_KEY must never be hardcoded. Understand which credentials are sensitive in your application.

  4. Static analysis should catch this automatically: Tools like Semgrep can flag any string constant with a name containing "SECRET", "KEY", or "PASSWORD". Integrate these checks into your CI/CD pipeline.

  5. Consider server-side request signing: Instead of signing requests on the client with a shared key, have the server issue time-limited tokens or use OAuth2 flows. This eliminates the need for client-side secrets entirely.

How Orbis AppSec Detected This

Source: The BIKA_SECRET_KEY constant definition at entry/src/main/ets/common/Constants.ets:65

Sink: The hardcoded string value '~d}$Q7$eIni=V)9\\RK/P.RM4;9[7|@/CA}b~OW!3?EV:<>M7pddUBL5n|0/*Cn'` exported as a public constant

Missing control: No obfuscation, encryption, or secure storage mechanism. The key was stored in plaintext, readable in source code and compiled binaries.

CWE: CWE-798 (Use of Hardcoded Credentials) and CWE-321 (Use of Hard-Coded Cryptographic Key)

Fix: Replaced the plaintext key with XOR-obfuscated byte arrays (BIKA_SECRET_KEY_ENC and BIKA_SECRET_KEY_MASK) decoded at runtime via decodeBikaSecretKey(), removing the plaintext credential from source code and compiled binaries.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

Hardcoded cryptographic keys represent one of the highest-risk vulnerabilities in client-side applications. The Bika application's exposure of BIKA_SECRET_KEY could have allowed attackers to forge API requests and compromise user accounts at scale.

The fix—runtime key deobfuscation using XOR masking—is a pragmatic short-term solution that prevents casual extraction while the application transitions to more secure key management practices. However, this is not a permanent solution. Organizations should:

  1. Audit all client-side code for hardcoded secrets
  2. Implement automated scanning in CI/CD pipelines
  3. Migrate to server-side authentication mechanisms where possible
  4. Use platform-specific secure storage for any necessary client-side credentials

By treating cryptographic keys with the same security rigor as passwords, developers can prevent entire classes of authentication bypass vulnerabilities. The fix in this PR demonstrates that even small changes—moving from plaintext to obfuscated storage—can significantly improve security posture.


Prevention and further reading

Frequently Asked Questions

Is storing a hardcoded key in a .env file enough?

No. .env files are still readable in source control and compiled binaries. For client-side apps, use platform-specific secure storage (Keychain on iOS, KeyStore on Android, secure enclave on HarmonyOS).

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #2

Related Articles

critical

How credential leakage through console logging happens in JavaScript browser extensions and how to fix it

A browser extension's `src/background/credentials.js` printed the full Strava authentication cookie string — including a signed JWT and CloudFront-Signature values — straight into the extension console via `console.debug`. Anyone who could open DevTools on the background page (or any tooling that scraped the console) could copy a live session and impersonate the user. The fix replaces the credential payload in both log statements with `Boolean(credentials)` and strips a realistic-looking JWT out

critical

How Hardcoded Secrets Compromise Authentication in JavaScript and How to Fix It

A critical vulnerability in `Tool/QuantumultX/Rewrite/RRSP.js` exposed hardcoded API authentication credentials—a TOKEN and UMID device identifier—directly in source code. Anyone with repository access could extract these credentials to impersonate the legitimate user and gain full account access to the RRTV API service. The fix replaced hardcoded secrets with empty placeholders, forcing users to manually configure credentials through secure channels.

critical

How API Key Exposure in Request Bodies happens in React and how to fix it

The Chatbot component in gitforme was transmitting Azure OpenAI API keys inside JSON request bodies, causing them to be logged by servers, proxies, and middleware. By moving the apiKey from requestBody.apiKey to an Authorization header, credentials are now protected from persistence in generic request logging infrastructure.

critical

How Hardcoded API Keys in WASM Modules Happen in KAP and How to Fix Them

A critical security vulnerability in `wasm/kap/standard-lib/fhelp-impl.kap` exposed hardcoded Gemini API keys directly in source code distributed to end users via WASM modules. The fix replaces the embedded credential with secure environment variable retrieval, preventing credential extraction through browser developer tools or binary inspection.

critical

How Hardcoded API Key Exposure Happens in Node.js and How to Fix It

A critical vulnerability in `archive/open_claude_code/src/api/client.mjs` exposed five different API providers' credentials through direct `process.env` access. The fix introduced a centralized `readApiKey()` function to enforce secure credential retrieval across Anthropic, OpenAI, Google, and other integrations.

high

How Insufficiently Protected Credentials happens in Node.js and how to fix it

A regex-based guard in `skills/xmemo/scripts/xmemo-skill.mjs` was supposed to block sensitive credentials from being passed as CLI flags, but it only matched a handful of keyword patterns—missing `password`, `client-secret`, `access-token`, `xmemo-key`, and more. The fix expands the blocklist regex to cover these additional credential patterns, closing a gap that could let secrets end up in shell history and process listings.