redis/redis-py · error · DataError

Hashed password {i} must be prefixed with a "+" to add or a

Error message

Hashed password {i} must be prefixed with a "+" to add or a "-" to remove

What it means

Raised by acl_setuser() as a DataError when an entry in hashed_passwords does not start with '+' (add) or '-' (remove). Hashed passwords are SHA-256 hex strings and, like plain passwords, must be prefixed so the client can encode them as #hash (add) or !hash (remove). The offending entry's index i is in the message. See redis/commands/core.py:506-516.

Source

Thrown at redis/commands/core.py:513

                    pieces.append(b"<%s" % password[1:])
                else:
                    raise DataError(
                        f"Password {i} must be prefixed with a "
                        f'"+" to add or a "-" to remove'
                    )

        if hashed_passwords:
            # as most users will have only one password, allow remove_passwords
            # to be specified as a simple string or a list
            hashed_passwords = list_or_args(hashed_passwords, [])
            for i, hashed_password in enumerate(hashed_passwords):
                hashed_password = encoder.encode(hashed_password)
                if hashed_password.startswith(b"+"):
                    pieces.append(b"#%s" % hashed_password[1:])
                elif hashed_password.startswith(b"-"):
                    pieces.append(b"!%s" % hashed_password[1:])
                else:
                    raise DataError(
                        f"Hashed password {i} must be prefixed with a "
                        f'"+" to add or a "-" to remove'
                    )

        if nopass:
            pieces.append(b"nopass")

        if categories:
            for category in categories:
                category = encoder.encode(category)
                # categories can be prefixed with one of (+@, +, -@, -)
                if category.startswith(b"+@"):
                    pieces.append(category)
                elif category.startswith(b"+"):
                    pieces.append(b"+@%s" % category[1:])
                elif category.startswith(b"-@"):
                    pieces.append(category)
                elif category.startswith(b"-"):

View on GitHub (pinned to da03cdc7e8)

Solutions

  1. Prefix each hash with '+' to add or '-' to remove, e.g. ['+5e884...'].
  2. Normalize on add: hashed_passwords = ['+' + h for h in raw_hashes].
  3. Confirm hashes are hex-encoded SHA-256; non-hex content will be rejected by the server separately.

Example fix

# before
r.acl_setuser('alice', enabled=True, hashed_passwords=['5e884898da28'])
# after
r.acl_setuser('alice', enabled=True, hashed_passwords=['+5e884898da28'])
Defensive patterns

Strategy: validation

Validate before calling

def _prefixed_hashes(items):
    for i, h in enumerate(items):
        if h[:1] not in ('+', '-'):
            raise ValueError(f'hashed password {i} needs +/- prefix')
    return items
client.acl_setuser(username, hashed_passwords=_prefixed_hashes(hashed_passwords or []))

Type guard

def hashed_passwords_are_prefixed(hashed_passwords) -> bool:
    return all(h[:1] in ('+', '-') for h in (hashed_passwords or []))

Try / catch

from redis.exceptions import DataError
try:
    client.acl_setuser(username, hashed_passwords=hashed_passwords)
except DataError:
    hashed_passwords = ['+' + h for h in hashed_passwords]
    client.acl_setuser(username, hashed_passwords=hashed_passwords)

Prevention

When it happens

Trigger: Calling r.acl_setuser(username, hashed_passwords=['5e88...']) (no prefix), or a list where any hash lacks +/- . Single prefixed string accepted via list_or_args.

Common situations: Pre-hashing passwords server-side or client-side and forgetting the prefix; integrating with an identity store that stores bare hashes; porting from a tool that does not require prefixes.

Related errors


AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04). Data as JSON: /data/errors/91300caf0de6c1cf.json. Report an issue: GitHub.