Skip to content

AxonOps — AI-Native Control Plane for Open Source Data Platforms

Kafka SASL/SCRAM Authentication

SCRAM (Salted Challenge Response Authentication Mechanism) provides secure password-based authentication for Kafka. It uses salted hashing and challenge-response to protect credentials during authentication without transmitting passwords.


FeatureBenefit
Salted hashesPasswords stored as salted hashes, not plaintext
Challenge-responsePassword never transmitted over network
No external dependenciesNo KDC, LDAP, or OAuth provider required
Dynamic credentialsUsers can be added/removed at runtime
Wide supportAvailable in all Kafka clients
AlgorithmHashSecurity LevelRecommendation
SCRAM-SHA-256SHA-256GoodAcceptable
SCRAM-SHA-512SHA-512BetterRecommended

Use SCRAM-SHA-512

Always prefer SCRAM-SHA-512 over SCRAM-SHA-256 for stronger security.

FeatureKafka Version
SCRAM-SHA-256/5120.10.2+
Dynamic credential updates0.10.2+
KRaft SCRAM bootstrap3.5.0+
Combined SCRAM + delegation tokens1.1.0+

SCRAM authentication exchange between client and brokerClientBrokerMetadataClientClientBrokerBrokerMetadataMetadataAuthenticationClientFirstMessage(username, nonce)Lookup user credentialsSalt, iterations, stored keyServerFirstMessage(salt, iterations, combined nonce)Compute:- SaltedPassword = PBKDF2(password, salt, iterations)- ClientKey = HMAC(SaltedPassword, "Client Key")- StoredKey = Hash(ClientKey)- ClientSignature = HMAC(StoredKey, AuthMessage)- ClientProof = ClientKey XOR ClientSignatureClientFinalMessage(ClientProof)Verify:- ClientSignature = HMAC(StoredKey, AuthMessage)- ClientKey = ClientProof XOR ClientSignature- StoredKey == Hash(ClientKey)?ServerFinalMessage(ServerSignature)Verify ServerSignatureAuthentication Complete

Key security properties:

  • Password is never transmitted over the network
  • Server stores only salted hash, not password
  • Both client and server mutually authenticate
  • Replay attacks prevented by nonces

server.properties
# Listener configuration
listeners=SASL_SSL://0.0.0.0:9093
advertised.listeners=SASL_SSL://kafka1.example.com:9093
# Security protocol for inter-broker communication
security.inter.broker.protocol=SASL_SSL
sasl.mechanism.inter.broker.protocol=SCRAM-SHA-512
# Enable SCRAM mechanism
sasl.enabled.mechanisms=SCRAM-SHA-512
# JAAS configuration for inter-broker authentication
listener.name.sasl_ssl.scram-sha-512.sasl.jaas.config=\
org.apache.kafka.common.security.scram.ScramLoginModule required \
username="kafka-broker" \
password="broker-secret-password";

SCRAM must be used with TLS encryption (SASL_SSL):

# SSL/TLS settings
ssl.keystore.type=PKCS12
ssl.keystore.location=/etc/kafka/ssl/kafka.keystore.p12
ssl.keystore.password=${KEYSTORE_PASSWORD}
ssl.key.password=${KEY_PASSWORD}
ssl.truststore.type=PKCS12
ssl.truststore.location=/etc/kafka/ssl/kafka.truststore.p12
ssl.truststore.password=${TRUSTSTORE_PASSWORD}
# Recommended TLS settings
ssl.enabled.protocols=TLSv1.3,TLSv1.2
ssl.endpoint.identification.algorithm=HTTPS

Never Use SASL_PLAINTEXT with SCRAM

While SCRAM doesn't transmit passwords, SASL_PLAINTEXT exposes all data to network sniffing. Always use SASL_SSL.

Enable multiple SASL mechanisms on the same listener:

# Enable both SCRAM and PLAIN (for migration)
sasl.enabled.mechanisms=SCRAM-SHA-512,PLAIN
# SCRAM JAAS config
listener.name.sasl_ssl.scram-sha-512.sasl.jaas.config=\
org.apache.kafka.common.security.scram.ScramLoginModule required \
username="kafka-broker" \
password="broker-password";
# PLAIN JAAS config (for legacy clients)
listener.name.sasl_ssl.plain.sasl.jaas.config=\
org.apache.kafka.common.security.plain.PlainLoginModule required \
username="kafka-broker" \
password="broker-password" \
user_kafka-broker="broker-password";

In ZooKeeper mode, SCRAM credentials are stored in ZooKeeper:

Terminal window
# Create user
kafka-configs.sh --zookeeper localhost:2181 \
--alter \
--add-config 'SCRAM-SHA-512=[password=user-password]' \
--entity-type users \
--entity-name my-user
# Create user with custom iterations
kafka-configs.sh --zookeeper localhost:2181 \
--alter \
--add-config 'SCRAM-SHA-512=[iterations=16384,password=user-password]' \
--entity-type users \
--entity-name my-user
# List users
kafka-configs.sh --zookeeper localhost:2181 \
--describe \
--entity-type users
# Delete user
kafka-configs.sh --zookeeper localhost:2181 \
--alter \
--delete-config 'SCRAM-SHA-512' \
--entity-type users \
--entity-name my-user

In KRaft mode, credentials are stored in the metadata log:

Terminal window
# Create user (using bootstrap server)
kafka-configs.sh --bootstrap-server kafka:9093 \
--command-config admin.properties \
--alter \
--add-config 'SCRAM-SHA-512=[password=user-password]' \
--entity-type users \
--entity-name my-user
# List users
kafka-configs.sh --bootstrap-server kafka:9093 \
--command-config admin.properties \
--describe \
--entity-type users
# Describe specific user (shows hash, not password)
kafka-configs.sh --bootstrap-server kafka:9093 \
--command-config admin.properties \
--describe \
--entity-type users \
--entity-name my-user

admin.properties:

security.protocol=SASL_SSL
sasl.mechanism=SCRAM-SHA-512
sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required \
username="admin" \
password="admin-password";
ssl.truststore.location=/etc/kafka/ssl/client.truststore.p12
ssl.truststore.password=truststore-password

For KRaft clusters, create initial credentials during storage formatting:

Terminal window
# Format storage with initial SCRAM credentials
kafka-storage.sh format \
-t $(kafka-storage.sh random-uuid) \
-c config/kraft/server.properties \
--add-scram 'SCRAM-SHA-512=[name=admin,password=admin-secret]' \
--add-scram 'SCRAM-SHA-512=[name=kafka-broker,password=broker-secret]'

KRaft Bootstrap Requirement

In KRaft mode, at least one SCRAM user must exist before brokers can authenticate. Use --add-scram during formatting to bootstrap.

ModeStorage LocationEncryption
ZooKeeper/config/users/{username}ZooKeeper ACLs
KRaftMetadata logCluster encryption

What's stored:

{
"SCRAM-SHA-512": {
"salt": "base64-encoded-salt",
"stored_key": "base64-encoded-stored-key",
"server_key": "base64-encoded-server-key",
"iterations": 4096
}
}

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import java.util.Properties;
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "kafka1:9093,kafka2:9093");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
// Security configuration
props.put("security.protocol", "SASL_SSL");
props.put("sasl.mechanism", "SCRAM-SHA-512");
props.put("sasl.jaas.config",
"org.apache.kafka.common.security.scram.ScramLoginModule required " +
"username=\"my-application\" " +
"password=\"app-password\";");
// TLS configuration
props.put("ssl.truststore.location", "/etc/kafka/ssl/client.truststore.p12");
props.put("ssl.truststore.password", "truststore-password");
props.put("ssl.truststore.type", "PKCS12");
// Recommended: verify broker hostname
props.put("ssl.endpoint.identification.algorithm", "HTTPS");
KafkaProducer<String, String> producer = new KafkaProducer<>(props);

application.yml:

spring:
kafka:
bootstrap-servers: kafka1:9093,kafka2:9093
properties:
security.protocol: SASL_SSL
sasl.mechanism: SCRAM-SHA-512
sasl.jaas.config: >
org.apache.kafka.common.security.scram.ScramLoginModule required
username="${KAFKA_USERNAME}"
password="${KAFKA_PASSWORD}";
ssl:
trust-store-location: classpath:truststore.p12
trust-store-password: ${TRUSTSTORE_PASSWORD}
trust-store-type: PKCS12
producer:
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
consumer:
group-id: my-consumer-group
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
from confluent_kafka import Producer
config = {
'bootstrap.servers': 'kafka1:9093,kafka2:9093',
'security.protocol': 'SASL_SSL',
'sasl.mechanism': 'SCRAM-SHA-512',
'sasl.username': 'my-application',
'sasl.password': 'app-password',
'ssl.ca.location': '/etc/kafka/ssl/ca-cert.pem',
# or for truststore:
# 'ssl.truststore.location': '/etc/kafka/ssl/client.truststore.p12',
# 'ssl.truststore.password': 'truststore-password',
}
producer = Producer(config)
import (
"github.com/confluentinc/confluent-kafka-go/v2/kafka"
)
producer, err := kafka.NewProducer(&kafka.ConfigMap{
"bootstrap.servers": "kafka1:9093,kafka2:9093",
"security.protocol": "SASL_SSL",
"sasl.mechanism": "SCRAM-SHA-512",
"sasl.username": "my-application",
"sasl.password": "app-password",
"ssl.ca.location": "/etc/kafka/ssl/ca-cert.pem",
})

client.properties:

security.protocol=SASL_SSL
sasl.mechanism=SCRAM-SHA-512
sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required \
username="my-user" \
password="user-password";
ssl.truststore.location=/etc/kafka/ssl/client.truststore.p12
ssl.truststore.password=truststore-password
Terminal window
# Produce messages
kafka-console-producer.sh --bootstrap-server kafka:9093 \
--topic my-topic \
--producer.config client.properties
# Consume messages
kafka-console-consumer.sh --bootstrap-server kafka:9093 \
--topic my-topic \
--consumer.config client.properties \
--from-beginning
# List topics
kafka-topics.sh --bootstrap-server kafka:9093 \
--command-config client.properties \
--list

RequirementRecommendation
LengthMinimum 16 characters
ComplexityMix of upper/lower/numbers/symbols
UniquenessDifferent password per user/service
StorageUse secrets management (Vault, K8s Secrets)
RotationRotate regularly (quarterly minimum)

Higher iteration counts increase PBKDF2 computation time, making brute-force attacks harder:

Terminal window
# Create user with high iteration count
kafka-configs.sh --bootstrap-server kafka:9093 \
--command-config admin.properties \
--alter \
--add-config 'SCRAM-SHA-512=[iterations=16384,password=user-password]' \
--entity-type users \
--entity-name my-user
IterationsSecurityPerformance Impact
4096 (default)Minimum acceptableLow
8192GoodModerate
16384BetterHigher
32768HighSignificant

Balance Security and Performance

Higher iterations slow down authentication. For high-throughput systems with many connections, balance security needs with performance.

Rotating user credentials:

Terminal window
# Step 1: Update credential (old sessions remain valid)
kafka-configs.sh --bootstrap-server kafka:9093 \
--command-config admin.properties \
--alter \
--add-config 'SCRAM-SHA-512=[password=new-password]' \
--entity-type users \
--entity-name my-user
# Step 2: Update client configuration with new password
# Step 3: Restart clients to pick up new credentials

Rotating broker credentials:

  1. Update JAAS configuration on all brokers
  2. Rolling restart brokers one at a time
  3. Monitor for authentication failures

HashiCorp Vault integration:

Terminal window
# Store credentials in Vault
vault kv put secret/kafka/my-user password="user-password"
# Retrieve in application
export KAFKA_PASSWORD=$(vault kv get -field=password secret/kafka/my-user)

Kubernetes Secrets:

apiVersion: v1
kind: Secret
metadata:
name: kafka-credentials
type: Opaque
stringData:
username: my-application
password: app-password
---
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: app
env:
- name: KAFKA_USERNAME
valueFrom:
secretKeyRef:
name: kafka-credentials
key: username
- name: KAFKA_PASSWORD
valueFrom:
secretKeyRef:
name: kafka-credentials
key: password

ErrorCauseSolution
SaslAuthenticationException: Authentication failedWrong username/passwordVerify credentials
SaslAuthenticationException: Unknown userUser doesn't existCreate user with kafka-configs
UnsupportedSaslMechanismExceptionSCRAM not enabled on brokerAdd to sasl.enabled.mechanisms
IllegalArgumentException: No serviceNameMissing JAAS configCheck sasl.jaas.config
SSLHandshakeExceptionTLS configuration issueVerify truststore

Client-side:

# log4j.properties or logback
log4j.logger.org.apache.kafka.common.security=DEBUG
log4j.logger.org.apache.kafka.common.security.scram=TRACE

Broker-side:

log4j.properties
log4j.logger.org.apache.kafka.common.security=DEBUG
log4j.logger.kafka.server=DEBUG
Terminal window
# List all users
kafka-configs.sh --bootstrap-server kafka:9093 \
--command-config admin.properties \
--describe \
--entity-type users
# Check specific user
kafka-configs.sh --bootstrap-server kafka:9093 \
--command-config admin.properties \
--describe \
--entity-type users \
--entity-name my-user
Terminal window
# Quick connectivity test
kafka-broker-api-versions.sh --bootstrap-server kafka:9093 \
--command-config client.properties
# If successful, shows broker API versions
# If failed, shows authentication error
Terminal window
# Look for authentication failures
grep -i "authentication\|sasl\|scram" /var/log/kafka/server.log | tail -50

  1. Create SCRAM credentials for all users:
Terminal window
kafka-configs.sh --bootstrap-server kafka:9092 \
--alter \
--add-config 'SCRAM-SHA-512=[password=user-password]' \
--entity-type users \
--entity-name existing-user
  1. Enable both mechanisms on brokers:
sasl.enabled.mechanisms=PLAIN,SCRAM-SHA-512
  1. Rolling restart brokers

  2. Update clients to use SCRAM:

sasl.mechanism=SCRAM-SHA-512
sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required \
username="existing-user" \
password="user-password";
  1. After all clients migrated, remove PLAIN:
sasl.enabled.mechanisms=SCRAM-SHA-512

SCRAM credentials in ZooKeeper are not automatically migrated to KRaft. Manual migration required:

  1. Export users from ZooKeeper cluster
  2. Create users in KRaft cluster using kafka-configs.sh
  3. Or bootstrap with kafka-storage.sh --add-scram