How RAGFlow Handles Passwords: Transport Encryption + One-Way Hashing
How RAGFlow Handles Passwords: Transport Encryption + One-Way Hashing
When reviewing RAGFlow’s authentication flow, one detail is especially important: passwords are not stored using reversible encryption. Instead, the system combines encrypted transport with one-way hashing at persistence time.
This post walks through that design and explains why it is practical for real-world deployments.
Security Model at a Glance
RAGFlow applies layered protection across the password lifecycle:
- Client-side transport protection: the password is encrypted with an RSA public key before being sent.
- Server-side validation and decoding: payloads are decrypted and validated in reset flows.
- Storage protection: the final password representation in the database is a hash generated by Werkzeug (
generate_password_hash), which is one-way.
This means transport confidentiality and storage safety are handled as separate concerns, which is a sound security architecture.
Password Flow in Practice
1) CLI Reset Path
In the CLI path, the new password is base64-encoded and then hashed:
Reference: commands.py:39-44
encode_password = base64.b64encode(new_password.encode('utf-8')).decode('utf-8')
password_hash = generate_password_hash(encode_password)
The key point is the hash step: once generated, the stored value cannot be reversed to plaintext.
2) Web Reset Path
In the web reset flow, the incoming encrypted payload is decrypted and base64-decoded for validation (for example, to ensure both password entries match):
Reference: user_app.py:1026-1046
new_pwd_base64 = decrypt(new_pwd)
new_pwd_string = base64.b64decode(new_pwd_base64).decode('utf-8')
# ...
UserService.update_user_password(user.id, new_pwd_base64)
Even though the API layer temporarily handles a base64 form during flow processing, persistence still follows the hashing strategy through the user service path.
3) Client-Side Encryption
The client encrypts the base64-encoded password using an RSA public key before transmission:
Reference: ragflow_client.py:39-44
def encrypt(input_string):
pub = "-----BEGIN PUBLIC KEY-----\n..."
pub_key = RSA.importKey(pub)
cipher = Cipher_pkcs1_v1_5.new(pub_key)
cipher_text = cipher.encrypt(base64.b64encode(input_string.encode("utf-8")))
return base64.b64encode(cipher_text).decode("utf-8")
This protects secrets in transit, especially when interacting with APIs from automation tools or admin clients.
Why This Design Works
- Strong breach posture: if the database is leaked, attackers get password hashes rather than plaintext.
- Clear separation of concerns: RSA handles transport confidentiality; hashing handles storage security.
- Operationally practical: hashing is a standard, battle-tested pattern compatible with normal login verification workflows.
Operational Notes
- One-way hashing is aligned with common password security best practices.
- Base64 is an encoding format, not encryption; the security guarantees come from RSA (transport) and hashing (storage).
- The default admin password
adminshould always be changed in production.