Skip to main content

Overview

The ZKTeco Biometric Server implements multiple layers of security:

Device Authentication

Password-based authentication for device connections using the ZKTeco protocol

SSL/TLS Encryption

Automatic certificate generation and HTTPS support for API communications

Device Authentication

Password Field

Each ZKTeco biometric device can be configured with a communication password. This password is stored in the device configuration:
Device Configuration
{
    "principal": {
        "ip": "192.168.1.205",
        "port": 4370,
        "password": 0,  # Device communication password
        "timeout": 5,
        "name": "Entrada Principal"
    }
}
Default Password: 0Most ZKTeco devices ship with password 0 (no password). Change this in your device’s admin settings for enhanced security.

How Device Authentication Works

The password is passed to the ZK library during connection establishment:
source/server.py
def conectar(device_id):
    cfg = DEVICES[device_id]
    zk = ZK(
        cfg["ip"], 
        port=cfg["port"], 
        timeout=cfg["timeout"],
        password=cfg["password"],  # Auth password here
        force_udp=False, 
        ommit_ping=False
    )
    return zk.connect()
Connection Flow:
1

TCP Connection

Client establishes TCP connection to device IP:port
2

Authentication Challenge

Device requests password if configured
3

Password Validation

ZK library sends password, device validates
4

Connection Established

On success, connection object returned for operations

Password Configuration

Single-Device Mode:
Environment Variable
export ZK_PASSWORD=12345
source/servidor.py
ZK_PASSWORD = int(os.getenv("ZK_PASSWORD", 0))
Multi-Device Mode: Each device can have its own password via the device registration API:
Register Device with Password
curl -X POST https://localhost:5000/devices \
  -H "Content-Type: application/json" \
  -d '{
    "id": "secure_entrance",
    "name": "Secure Entrance",
    "ip": "192.168.1.210",
    "password": 12345
  }'
Update Device Password
curl -X PUT https://localhost:5000/devices/secure_entrance \
  -H "Content-Type: application/json" \
  -d '{"password": 67890}'
Password StorageDevice passwords are stored in plaintext in devices.json. Protect this file with appropriate filesystem permissions:
chmod 600 devices.json
chown zkteco:zkteco devices.json

SSL/TLS Configuration

Automatic Certificate Generation

Both server implementations include automatic SSL certificate generation using PyOpenSSL:
source/server.py
def generar_certificado():
    if os.path.exists(CERT_FILE) and os.path.exists(KEY_FILE):
        log.info("Certificados SSL encontrados.")
        return True
    
    try:
        from OpenSSL import crypto
        
        # Generate 2048-bit RSA key
        k = crypto.PKey()
        k.generate_key(crypto.TYPE_RSA, 2048)
        
        # Create self-signed certificate
        cert = crypto.X509()
        cert.get_subject().CN = "ZKTeco Server"
        cert.set_serial_number(1)
        cert.gmtime_adj_notBefore(0)
        cert.gmtime_adj_notAfter(365 * 24 * 60 * 60)  # 1 year validity
        cert.set_issuer(cert.get_subject())
        cert.set_pubkey(k)
        cert.sign(k, "sha256")
        
        # Write certificate and key to disk
        with open(CERT_FILE, "wb") as f:
            f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
        with open(KEY_FILE, "wb") as f:
            f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, k))
        
        log.info("Certificados SSL generados.")
        return True
    except ImportError:
        log.warning("pyopenssl no instalado. Corriendo en HTTP.")
        return False
    except Exception as e:
        log.error(f"Error SSL: {e}")
        return False

Certificate Properties

Algorithm
RSA 2048-bit
Industry-standard key strength balancing security and performance
Signature
SHA-256
Secure hashing algorithm for certificate signing
Validity
365 days
Certificate expires after one year and must be regenerated
Common Name
ZKTeco Server
Certificate CN identifying the server
Type
Self-signed
Certificate signed by its own key (not a CA-issued certificate)

Certificate File Locations

Certificates are stored in the server’s working directory by default:
source/server.py
CERT_FILE = os.getenv("CERT_FILE", "cert.pem")
KEY_FILE = os.getenv("KEY_FILE", "key.pem")
Customize via environment:
export CERT_FILE="/etc/zkteco/ssl/cert.pem"
export KEY_FILE="/etc/zkteco/ssl/key.pem"

Using Custom Certificates

For production deployments, replace self-signed certificates with CA-issued certificates:
1

Obtain CA Certificate

Get a certificate from Let’s Encrypt, DigiCert, or your organization’s CA
2

Place Certificate Files

Copy cert.pem and key.pem to your server
3

Set Permissions

chmod 600 /etc/zkteco/ssl/key.pem
chmod 644 /etc/zkteco/ssl/cert.pem
4

Configure Paths

export CERT_FILE="/etc/zkteco/ssl/cert.pem"
export KEY_FILE="/etc/zkteco/ssl/key.pem"
5

Restart Server

The server will detect and use your custom certificates
Let’s Encrypt IntegrationFor automatic certificate renewal, consider using a reverse proxy like Nginx or Caddy with Let’s Encrypt support, and run the ZKTeco server in HTTP mode behind the proxy.

Server Startup with SSL

The server automatically configures SSL based on certificate availability:
source/server.py
if __name__ == "__main__":
    ssl_ok = generar_certificado()
    proto = "https" if ssl_ok else "http"
    
    log.info(f"Servidor ZKTeco — Multi-dispositivo dinamico")
    log.info(f"{proto}://{API_HOST}:{API_PORT}")
    
    if ssl_ok:
        app.run(
            host=API_HOST, 
            port=API_PORT, 
            debug=False, 
            ssl_context=(CERT_FILE, KEY_FILE)
        )
    else:
        app.run(
            host=API_HOST, 
            port=API_PORT, 
            debug=False
        )
Startup Flow:

Startup Logs

Successful HTTPS:
2026-03-06 14:30:15 [INFO] Certificados SSL generados.
2026-03-06 14:30:15 [INFO] Servidor ZKTeco — Multi-dispositivo dinamico
2026-03-06 14:30:15 [INFO] https://0.0.0.0:5000
Fallback to HTTP:
2026-03-06 14:30:15 [WARNING] pyopenssl no instalado. Corriendo en HTTP.
2026-03-06 14:30:15 [INFO] Servidor ZKTeco — Multi-dispositivo dinamico
2026-03-06 14:30:15 [INFO] http://0.0.0.0:5000

Client Connection Security

Accepting Self-Signed Certificates

When using self-signed certificates, clients must be configured to accept them:
import requests

# Development: Disable SSL verification
response = requests.get(
    "https://localhost:5000/devices",
    verify=False
)

# Production: Specify certificate path
response = requests.get(
    "https://localhost:5000/devices",
    verify="/path/to/cert.pem"
)
Production SecurityNever disable SSL verification in production. Always use proper CA-issued certificates or add your self-signed certificate to the client’s trust store.

Network Security Best Practices

Firewall Configuration

Protect your ZKTeco server with firewall rules:
UFW (Ubuntu)
# Allow only from specific network
sudo ufw allow from 192.168.1.0/24 to any port 5000

# Or allow specific IP
sudo ufw allow from 192.168.1.100 to any port 5000
iptables
# Allow only from local network
iptables -A INPUT -p tcp -s 192.168.1.0/24 --dport 5000 -j ACCEPT
iptables -A INPUT -p tcp --dport 5000 -j DROP

Device Network Isolation

API Authentication (Future Enhancement)

The current implementation does not include API authentication. Consider implementing:

API Keys

Add API key validation middleware to protect endpoints from unauthorized access

OAuth 2.0

Implement OAuth for enterprise integrations with token-based authentication

JWT Tokens

Use JSON Web Tokens for stateless session management

Rate Limiting

Prevent abuse with request rate limiting per client/IP
Example API Key Middleware:
from functools import wraps

API_KEY = os.getenv("API_KEY", "your-secure-key-here")

def require_api_key(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        key = request.headers.get('X-API-Key')
        if key != API_KEY:
            return _json({"error": "Invalid or missing API key"}, 401)
        return f(*args, **kwargs)
    return decorated

@app.route("/devices", methods=["GET"])
@require_api_key
def list_devices():
    # Protected endpoint
    pass

Connection Timeout Security

Each device connection has a configurable timeout to prevent hanging:
source/server.py
{
    "timeout": 5  # Connection timeout in seconds
}
Benefits:
  • Prevents indefinite hangs on unreachable devices
  • Limits resource consumption from slow connections
  • Fails fast for better user experience
Adjust timeout based on network conditions:
  • Local network: 3-5 seconds
  • VPN/Remote: 10-15 seconds
  • Unreliable network: 20-30 seconds

Security Checklist

1

✓ Set device passwords

Configure non-zero passwords on all ZKTeco devices
2

✓ Isolate device network

Keep biometric devices on separate VLAN/subnet
3

✓ Disable unused device features

Turn off unnecessary device services and ports
1

✓ Use CA-issued certificates

Replace self-signed certificates in production
2

✓ Implement API authentication

Add API key or OAuth protection to endpoints
3

✓ Configure firewall rules

Restrict API access to trusted networks/IPs
4

✓ Secure configuration files

Set proper permissions on devices.json and certificate files
5

✓ Enable logging

Monitor and audit API access patterns
1

✓ Protect devices.json

Contains passwords - use filesystem encryption if needed
2

✓ Secure backup procedures

Encrypt backups containing device configurations
3

✓ Implement data retention policies

Determine how long to store attendance records
Next Steps:

Build docs developers (and LLMs) love