Secure Password Hashing in Oracle PL/SQL using HMAC-SHA512

Mahdi AhmadiMahdi Ahmadi
4 min read

1. Introduction

Password hashing is a critical part of building secure applications. In Oracle environments where advanced hashing functions like PKCS5_PBKDF2 are not readily available, it becomes essential to implement custom cryptographic solutions using native PL/SQL tools.

In this post, we introduce a custom PL/SQL package called irap_crypto_pkg, designed to provide secure password hashing using HMAC-SHA512, dynamic salts, and a repeatable hashing method as a practical alternative to PBKDF2.


2. Package Specification: irap_crypto_pkg

Below is the specification of the package, outlining its available functions:

CREATE OR REPLACE PACKAGE irap_crypto_pkg AS
  -- Generate a random salt
  FUNCTION generate_salt RETURN VARCHAR2;

  -- Create a password hash using HMAC-SHA512 and salt
  FUNCTION generate_hash(
    p_password IN VARCHAR2,
    p_salt     IN VARCHAR2
  ) RETURN VARCHAR2;

  -- Alternative implementation of PBKDF2 using repeated HMAC
  FUNCTION pbkdf2_alternative(
    p_password    IN VARCHAR2,
    p_salt        IN VARCHAR2,
    p_iterations  IN NUMBER DEFAULT 10000,
    p_key_length  IN NUMBER DEFAULT 32
  ) RETURN VARCHAR2;

  -- Validate hash strength
  FUNCTION is_hash_strong_enough(
    p_hash IN VARCHAR2
  ) RETURN BOOLEAN;
END irap_crypto_pkg;
/

3. Package Body Implementation

The body uses built-in Oracle packages such as DBMS_CRYPTO, UTL_I18N, and RAWTOHEX for cryptographic operations.

CREATE OR REPLACE PACKAGE BODY irap_crypto_pkg AS

  FUNCTION generate_salt RETURN VARCHAR2 IS
    v_salt VARCHAR2(255);
  BEGIN
    v_salt := RAWTOHEX(SYS.DBMS_CRYPTO.RANDOMBYTES(32));
    RETURN v_salt;
  END generate_salt;

  FUNCTION generate_hash(
    p_password IN VARCHAR2,
    p_salt     IN VARCHAR2
  ) RETURN VARCHAR2 IS
    v_result RAW(4000);
    v_iterations NUMBER := 10000;
  BEGIN
    v_result := SYS.DBMS_CRYPTO.MAC(
      src => UTL_I18N.STRING_TO_RAW(p_salt || p_password, 'AL32UTF8'),
      typ => SYS.DBMS_CRYPTO.HMAC_SH512,
      key => UTL_I18N.STRING_TO_RAW(p_salt, 'AL32UTF8')
    );

    FOR i IN 1 .. v_iterations - 1 LOOP
      v_result := SYS.DBMS_CRYPTO.MAC(
        src => v_result,
        typ => SYS.DBMS_CRYPTO.HMAC_SH512,
        key => UTL_I18N.STRING_TO_RAW(p_salt, 'AL32UTF8')
      );
    END LOOP;

    RETURN RAWTOHEX(v_result);
  END generate_hash;

  FUNCTION pbkdf2_alternative(
    p_password    IN VARCHAR2,
    p_salt        IN VARCHAR2,
    p_iterations  IN NUMBER DEFAULT 10000,
    p_key_length  IN NUMBER DEFAULT 32
  ) RETURN VARCHAR2 IS
    v_result RAW(4000);
  BEGIN
    v_result := SYS.DBMS_CRYPTO.MAC(
      src => UTL_I18N.STRING_TO_RAW(p_salt || '1', 'AL32UTF8'),
      typ => SYS.DBMS_CRYPTO.HMAC_SH512,
      key => UTL_I18N.STRING_TO_RAW(p_password, 'AL32UTF8')
    );

    FOR i IN 2 .. p_iterations LOOP
      v_result := SYS.DBMS_CRYPTO.MAC(
        src => v_result,
        typ => SYS.DBMS_CRYPTO.HMAC_SH512,
        key => UTL_I18N.STRING_TO_RAW(p_password, 'AL32UTF8')
      );
    END LOOP;

    IF p_key_length < 64 THEN
      v_result := UTL_RAW.SUBSTR(v_result, 1, p_key_length);
    END IF;

    RETURN RAWTOHEX(v_result);
  END pbkdf2_alternative;

  FUNCTION is_hash_strong_enough(
    p_hash IN VARCHAR2
  ) RETURN BOOLEAN IS
  BEGIN
    RETURN LENGTH(p_hash) >= 64;
  END is_hash_strong_enough;

END irap_crypto_pkg;
/

4. Function Descriptions

generate_salt

  • Purpose: Generates a 32-byte random salt.

  • Usage: Helps prevent rainbow table attacks.

  • Example:

DECLARE
  v_salt VARCHAR2(255);
BEGIN
  v_salt := irap_crypto_pkg.generate_salt();
  DBMS_OUTPUT.PUT_LINE(v_salt);
END;

generate_hash

  • Purpose: Hashes a password using HMAC-SHA512 and salt, repeated 10,000 times.

  • Advantage: Computationally expensive to slow down brute-force attempts.

  • Example:

DECLARE
  v_hash VARCHAR2(4000);
BEGIN
  v_hash := irap_crypto_pkg.generate_hash('MyPassword123', 'ABC123SALT...');
  DBMS_OUTPUT.PUT_LINE(v_hash);
END;

pbkdf2_alternative

  • Purpose: Custom implementation mimicking PBKDF2.

  • Parameters:

    • p_iterations: Number of HMAC iterations (default: 10,000)

    • p_key_length: Desired key length in bytes (default: 32)

  • Example:

DECLARE
  v_pbkdf2_hash VARCHAR2(4000);
BEGIN
  v_pbkdf2_hash := irap_crypto_pkg.pbkdf2_alternative(
    p_password   => 'StrongPass!',
    p_salt       => 'F7A1B2C3D4...',
    p_iterations => 15000,
    p_key_length => 48
  );
  DBMS_OUTPUT.PUT_LINE(v_pbkdf2_hash);
END;

is_hash_strong_enough

  • Purpose: Verifies if a given hash meets the minimum required strength.

  • Example:

DECLARE
  v_ok BOOLEAN;
BEGIN
  v_ok := irap_crypto_pkg.is_hash_strong_enough('ABCDEF...');
  IF v_ok THEN
    DBMS_OUTPUT.PUT_LINE('Hash is strong.');
  ELSE
    DBMS_OUTPUT.PUT_LINE('Hash is weak!');
  END IF;
END;

5. Security Best Practices

RecommendationDescription
Use SaltAlways generate unique salts per user/password.
Repeat the HashingIncreases resistance against brute-force attacks.
Avoid MD5/SHA1These algorithms are obsolete and insecure.
Validate Hash LengthEnsure minimum strength before storage.
Tune IterationsUse higher iterations (e.g., 100,000) for high-security systems.

6. Limitations & Future Improvements

  • This version is not a full RFC-compliant PBKDF2 implementation.

  • However, it offers practical cryptographic strength using native PL/SQL.

  • In newer Oracle versions, consider using PKCS5_PBKDF2, ARGON2, or SCRYPT.

  • For multi-block key derivation, extend this implementation to handle block chaining.


7. Conclusion

The irap_crypto_pkg package provides a secure, modular, and practical solution for password hashing in Oracle PL/SQL—especially in environments where advanced cryptographic functions are unavailable.

0
Subscribe to my newsletter

Read articles from Mahdi Ahmadi directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Mahdi Ahmadi
Mahdi Ahmadi

Founder & CEO at Artabit | Oracle APEX Expert | Building Innovative HR Solutions | UAE & Iran