> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nullpass.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# IP Encryption

> User-specific IP address encryption for audit logs

<Warning>
  **Internal Documentation Only:** If you're not a Null Tools developer, you can close this documentation or visit the [Apps section](/apps/coming-soon) to learn more about using Null Pass in your applications.
</Warning>

## Overview

IP addresses in audit logs are encrypted using AES-256-GCM encryption with user-specific keys. This ensures that IP addresses are stored securely and can only be decrypted by the user who owns the log entry.

## Security Features

* **User-specific encryption**: Each user's IP addresses are encrypted with a key derived from their user ID
* **AES-256-GCM**: Industry-standard authenticated encryption
* **PBKDF2 key derivation**: 100,000 iterations with SHA-256
* **Automatic decryption**: IP addresses are automatically decrypted when retrieved via the audit logs API

## Implementation

### Code Reference

```1:113:nullpass_clean/src/lib/ip-utils.ts theme={null}
import { NextRequest } from 'next/server'
import crypto from 'crypto'

const IP_ENCRYPTION_SECRET = process.env.IP_ENCRYPTION_SECRET
const ALGORITHM = 'aes-256-gcm'
const IV_LENGTH = 16

function deriveKey(userId: string): Buffer {
  const keyMaterial = `${userId}:${IP_ENCRYPTION_SECRET}`
  return crypto.pbkdf2Sync(keyMaterial, 'ip-encryption-salt', 100000, 32, 'sha256')
}

export function encryptIp(ip: string, userId: string): string {
  if (ip === 'unknown') {
    return 'unknown'
  }

  try {
    const key = deriveKey(userId)
    const iv = crypto.randomBytes(IV_LENGTH)
    const cipher = crypto.createCipheriv(ALGORITHM, key, iv)

    let encrypted = cipher.update(ip, 'utf8', 'hex')
    encrypted += cipher.final('hex')
    const tag = cipher.getAuthTag()

    return `${iv.toString('hex')}:${tag.toString('hex')}:${encrypted}`
  } catch (error) {
    return 'unknown'
  }
}

export function decryptIp(encryptedIp: string, userId: string): string {
  if (encryptedIp === 'unknown' || !encryptedIp.includes(':')) {
    return encryptedIp
  }

  try {
    const parts = encryptedIp.split(':')
    if (parts.length !== 3) {
      return 'unknown'
    }

    const [ivHex, tagHex, encrypted] = parts
    const iv = Buffer.from(ivHex, 'hex')
    const tag = Buffer.from(tagHex, 'hex')
    const key = deriveKey(userId)

    const decipher = crypto.createDecipheriv(ALGORITHM, key, iv)
    decipher.setAuthTag(tag)

    let decrypted = decipher.update(encrypted, 'hex', 'utf8')
    decrypted += decipher.final('utf8')

    return decrypted
  } catch (error) {
    return 'unknown'
  }
}

export function getClientIpFromHeaders(getHeader: (name: string) => string | null): string {
  const clientIp = getHeader('x-client-ip')
  if (clientIp) {
    if (/^(\d{1,3}\.){3}\d{1,3}$/.test(clientIp)) {
      return clientIp
    }
    return clientIp
  }
  
  const forwardedFor = getHeader('x-forwarded-for')
  const realIp = getHeader('x-real-ip')
  
  if (forwardedFor) {
    const ips = forwardedFor.split(',').map(ip => ip.trim())
    
    const ipv4 = ips.find(ip => {
      return /^(\d{1,3}\.){3}\d{1,3}$/.test(ip)
    })
    
    if (ipv4) {
      return ipv4
    }
    
    if (ips.length > 0) {
      return ips[0]
    }
  }
  
  if (realIp) {
    if (/^(\d{1,3}\.){3}\d{1,3}$/.test(realIp)) {
      return realIp
    }
    return realIp
  }
  
  return 'unknown'
}

export function getClientIp(request: NextRequest): string {
  return getClientIpFromHeaders((name: string) => request.headers.get(name))
}

export function getClientIpForStorageFromHeaders(
  getHeader: (name: string) => string | null,
  userId: string
): string {
  const rawIp = getClientIpFromHeaders(getHeader)
  return encryptIp(rawIp, userId)
}

export function getClientIpForStorage(request: NextRequest, userId: string): string {
  return getClientIpForStorageFromHeaders((name: string) => request.headers.get(name), userId)
}
```

## Functions

### `encryptIp(ip: string, userId: string): string`

Encrypts an IP address using a user-specific key.

**Parameters:**

* `ip`: The IP address to encrypt (or `'unknown'`)
* `userId`: The user ID used for key derivation

**Returns:** Encrypted IP string in format `iv:tag:encrypted` or `'unknown'`

**Example:**

```typescript theme={null}
const encrypted = encryptIp('192.168.1.1', 'user_123')
// Returns: "a1b2c3d4...:e5f6g7h8...:i9j0k1l2..."
```

### `decryptIp(encryptedIp: string, userId: string): string`

Decrypts an encrypted IP address using a user-specific key.

**Parameters:**

* `encryptedIp`: The encrypted IP string (format `iv:tag:encrypted`)
* `userId`: The user ID used for key derivation

**Returns:** Decrypted IP address or `'unknown'` if decryption fails

**Example:**

```typescript theme={null}
const decrypted = decryptIp('a1b2c3d4...:e5f6g7h8...:i9j0k1l2...', 'user_123')
// Returns: "192.168.1.1"
```

### `getClientIp(request: NextRequest): string`

Extracts the client IP address from request headers.

**Priority order:**

1. `x-client-ip` header
2. First IPv4 address from `x-forwarded-for` header
3. First address from `x-forwarded-for` header
4. `x-real-ip` header
5. `'unknown'` if no IP found

**Example:**

```typescript theme={null}
const ip = getClientIp(request)
// Returns: "192.168.1.1" or "unknown"
```

### `getClientIpForStorage(request: NextRequest, userId: string): string`

Extracts and encrypts the client IP address for database storage.

**Example:**

```typescript theme={null}
const encryptedIp = getClientIpForStorage(request, userId)
// Returns encrypted IP ready for storage
```

## Encryption Format

Encrypted IP addresses are stored in the format:

```
{iv}:{tag}:{encrypted}
```

Where:

* `iv`: Initialization vector (hex, 16 bytes)
* `tag`: Authentication tag (hex, 16 bytes)
* `encrypted`: Encrypted IP address (hex)

## Key Derivation

Keys are derived using PBKDF2:

* **Algorithm**: SHA-256
* **Iterations**: 100,000
* **Key length**: 32 bytes (256 bits)
* **Salt**: `'ip-encryption-salt'`
* **Key material**: `{userId}:{IP_ENCRYPTION_SECRET}`

## Environment Variables

<ResponseField name="IP_ENCRYPTION_SECRET" type="string" required>
  Secret key used for IP encryption key derivation. Must be set in production and kept secure.
</ResponseField>

## Usage in Audit Logs

IP addresses are automatically encrypted when stored in audit logs:

```typescript theme={null}
const encryptedIp = getClientIpForStorage(request, userId)
await createAuditLog({
  userId,
  action: 'USER_LOGIN',
  data: {
    ip: encryptedIp, // Encrypted IP stored in database
    // ... other data
  }
})
```

When retrieving audit logs via the API, IP addresses are automatically decrypted:

```typescript theme={null}
const logs = await prisma.auditLog.findMany({ where: { userId } })
const logsWithDecryptedIp = logs.map(log => {
  const data = log.data as any
  if (data?.ip) {
    return {
      ...log,
      data: {
        ...data,
        ip: decryptIp(data.ip, userId), // Decrypted for API response
      }
    }
  }
  return log
})
```

## Security Considerations

* **User isolation**: Each user's IP addresses are encrypted with a unique key derived from their user ID
* **Forward secrecy**: If `IP_ENCRYPTION_SECRET` is compromised, existing encrypted IPs cannot be decrypted without the user ID
* **Authentication**: GCM mode provides authentication, ensuring encrypted IPs cannot be tampered with
* **Fallback**: Invalid or corrupted encrypted IPs return `'unknown'` instead of throwing errors

<Tip>
  IP addresses in audit log API responses are automatically decrypted. You don't need to manually decrypt them when using the audit logs endpoint.
</Tip>
