import java.nio.charset.StandardCharsets;
import java.util.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * A pepper is a server-side secret mixed into every password before hashing.
 * Here: HMAC-SHA256(pepper, password) -> base64 (44 chars, always under bcrypt's 72-byte limit)
 * -> bcrypt. A stolen database alone cannot be cracked without also stealing the pepper.
 * Side effect: it also removes the 72-byte truncation problem.
 */
public class PepperedEncoder implements PasswordEncoder {

    private final byte[] pepper;
    private final PasswordEncoder delegate;

    public PepperedEncoder(byte[] pepper, PasswordEncoder delegate) {
        this.pepper = pepper.clone();
        this.delegate = delegate;
    }

    private String prehash(CharSequence raw) {
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(pepper, "HmacSHA256"));
            byte[] out = mac.doFinal(raw.toString().getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(out);
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    @Override public String encode(CharSequence raw) { return delegate.encode(prehash(raw)); }

    @Override public boolean matches(CharSequence raw, String stored) { return delegate.matches(prehash(raw), stored); }

    @Override public boolean upgradeEncoding(String stored) { return delegate.upgradeEncoding(stored); }

    public static void main(String[] args) {
        byte[] pepper = "demo-pepper-load-me-from-a-secret-store".getBytes(StandardCharsets.UTF_8);
        PasswordEncoder withPepper = new PepperedEncoder(pepper, new BCryptPasswordEncoder(10));
        PasswordEncoder otherPepper = new PepperedEncoder("a-different-pepper-xxxxxxxxxxxxxxxxxxxx".getBytes(StandardCharsets.UTF_8), new BCryptPasswordEncoder(10));

        String longPw = "B".repeat(100);
        String h = withPepper.encode(longPw);
        System.out.println("100-char password with pepper: matches=" + withPepper.matches(longPw, h));
        System.out.println("same password, first 72 chars only: matches=" + withPepper.matches("B".repeat(72), h) + "  (truncation gone)");
        System.out.println("stolen DB + wrong pepper: matches=" + otherPepper.matches(longPw, h));
    }
}
