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.


References

Frequently Asked Questions

What is a hardcoded credential vulnerability?

A hardcoded credential is a secret (password, API key, signing key) embedded directly in source code or binaries. Attackers can extract it through reverse engineering and use it to impersonate the application.

How do you prevent hardcoded credentials in TypeScript/HarmonyOS?

Never embed secrets in code. Use secure storage (Keychain, secure storage APIs), environment variables, or server-side authentication. If client-side secrets are necessary, obfuscate them and decode at runtime.

What CWE is this vulnerability?

CWE-798: Use of Hardcoded Credentials. This is one of the most exploitable vulnerabilities in client-side applications.

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).

Can static analysis detect hardcoded credentials?

Yes. Tools like Semgrep, TruffleHog, and GitGuardian detect patterns like API keys, signing keys, and passwords in code. The fix in this PR shows why obfuscation alone is insufficient—the key must be managed securely.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #2

Related Articles

critical

How API Key Exposure in URL Parameters happens in Python and how to fix it

The Wine Cellar Home Assistant integration exposed Gemini API keys by transmitting them as URL query parameters in HTTP requests. This critical vulnerability allowed API keys to be logged in server logs, proxy caches, and browser history. The fix moved authentication to the secure `x-goog-api-key` HTTP header, preventing credential leakage.

critical

How Hardcoded API Keys Happen in TOML Configuration Files and How to Fix Them

A hardcoded Google Maps API key was discovered in `exampleSite/config/_default/params.toml` at line 113, exposing a live credential that any attacker could extract from the repository and use to make unauthorized API calls. This critical vulnerability was automatically detected and fixed by replacing the hardcoded key with an empty placeholder, eliminating the risk of credential theft and unauthorized usage charges.

critical

How Plaintext Secret Storage Happens in Cloudflare Workers (wrangler.toml) and How to Fix It

A critical misconfiguration in `platforms/m365/wrangler.toml` left developers one copy-paste away from committing live API keys directly into git history. The fix adds an explicit warning comment blocking the `[vars]` anti-pattern and adds `.dev.vars` to `.gitignore`, ensuring secrets flow through Cloudflare's encrypted `wrangler secret` mechanism instead of plaintext config. This matters because git history is permanent — a key committed even once can be extracted long after it's "deleted."

critical

How Hardcoded API Keys Happen in JavaScript and How to Fix Them

A critical security vulnerability was discovered in `src/js/init.js` where a Bugsnag API key was hardcoded directly into client-side JavaScript, making it visible to anyone who inspects the page source or JavaScript bundle. The fix replaces the hardcoded string with an environment variable reference (`import.meta.env.VITE_BUGSNAG_API_KEY`), ensuring the key is injected at build time rather than baked into the shipped code. This pattern is one of the most common — and most avoidable — secrets exp

high

How Hardcoded API Keys happen in JavaScript and how to fix it

A critical security vulnerability was discovered in `javascripts/common.js` where Firebase API keys, auth domains, and sender IDs were hardcoded directly in client-side JavaScript. Any user who opened browser DevTools or viewed page source could extract these credentials and make unauthorized calls to the Firebase Realtime Database and Yandex Translation services. The fix moves all sensitive configuration values to environment variables, ensuring secrets never reach the client bundle.

critical

How Cross-Site Scripting happens in fast-xml-parser and how to fix it

CVE-2026-25896 is a critical Cross-Site Scripting vulnerability in fast-xml-parser stemming from improper DOCTYPE entity handling, which could allow attackers to inject malicious scripts through crafted XML payloads. The fix upgrades the vulnerable dependency from version 4.4.1 to patched versions 5.3.5 and 4.5.4, eliminating the unsafe parsing behavior while preserving all legitimate XML processing functionality.