1
0
mirror of https://github.com/imgproxy/imgproxy.git synced 2025-02-02 11:34:20 +02:00

Encrypted source URLs docs (#962)

* Encrypted source URLs docs

* Apply suggestions from code review

Co-authored-by: Travis-Turner <32389151+Travis-Turner@users.noreply.github.com>

Co-authored-by: Travis-Turner <32389151+Travis-Turner@users.noreply.github.com>
This commit is contained in:
Sergey Alexandrovich 2022-09-02 14:17:43 +06:00 committed by GitHub
parent 481ab3b78f
commit 4bd9c37884
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 255 additions and 6 deletions

View File

@ -1,6 +1,8 @@
# Changelog
## [Unreleased]
### Add
- (pro) Add encrypted source URL support.
## [3.7.2] - 2022-08-22
### Changed

View File

@ -5,6 +5,7 @@
* [Generating the URL](generating_the_url)
* [Getting the image info<img title="imgproxy Pro feature" src="/assets/pro.svg">](getting_the_image_info)
* [Signing the URL](signing_the_url)
* [Encrypting the source URL<img src="/assets/pro.svg">](encrypting_the_source_url)
* [Watermark](watermark)
* [Presets](presets)
* [Object detection<img title="imgproxy Pro feature" src="/assets/pro.svg">](object_detection)

View File

@ -79,6 +79,12 @@ You can also specify a secret key to enable authorization with the HTTP `Authori
* `IMGPROXY_SECRET`: the authorization token. If specified, the HTTP request should contain the `Authorization: Bearer %secret%` header.
If you don't want to reveal your source URLs, you can encrypt them with the AES-CBC algorithm:
* `IMGPROXY_SOURCE_URL_ENCRYPRION_KEY`: hex-encoded key used for source URL encryption. Default: blank
**📝Note:** Read more about source URL encryption in the [encrypting the source URL guide](encrypting_the_source_url.md).
imgproxy does not send CORS headers by default. CORS will need to be allowed by uisng the following variable:
* `IMGPROXY_ALLOW_ORIGIN`: when specified, enables CORS headers with the provided origin. CORS headers are disabled by default.

View File

@ -0,0 +1,98 @@
# Encrypting the source URL![pro](/assets/pro.svg)
If you don't want to reveal your source URLs, you can encrypt them with the AES-CBC algorithm.
### Configuring source URL encryption
The only thing needed for source URL encryption is a key:
* `IMGPROXY_SOURCE_URL_ENCRYPRION_KEY`: hex-encoded key used for source URL encryption. Default: blank
The key should be either 16, 24, or 32 bytes long for AES-128-CBC, AES-192-CBC, or AES-256-CBC, respectively.
If you need a random key in a hurry, you can quickly generate one using the following snippet:
```bash
echo $(xxd -g 2 -l 32 -p /dev/random | tr -d '\n')
```
### Encrypting the source URL
* Pad your source URL using the [PKCS #7](https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS#5_and_PKCS#7) method so it becomes 16-byte aligned. Some libraries like Ruby's `openssl` do the message padding for you
* Generate a 16-byte long initialization vector (IV)
* Encrypt the padded source URL with the AES-CBC algorithm using the configured key and the IV generated in the previous step
* Create the following string: IV + encrypted URL
* Encode the result of the previous step with URL-safe Base64
#### IV generation
AES-CBC requires IV to be unique between unencrypted messages (source URLs in our case). Usually, it's recommended to use a counter when generating an IV to be sure it never repeats. However, in our case, this leads to a major drawback: using a unique IV every time you encrypt the same source URL will lead to different cipher texts and thus different imgproxy URLs. And this leads to a situation where requests to imgproxy will never hit the CDNs cache.
On the other hand, reusing the IV with the same message is safe but ONLY while with this message. Thus, there are some tradeoffs:
1. Cache IVs. Store IV somewhere so you need to generate it only once for each source URL and extract it if needed. Depending on the level of security you need, you may also want to encrypt stored IVs with a different key so a DB leak won't reveal the message-IV pairs.
2. Use a deterministic method of generation. For example, you can calculate an HMAC hash of the plain source URL with a different key and truncate it to the IV size. Though this method doesn't guarantee that it will always generate unique IVs, the chances of generating repeatable IVs with it are considerably rare.
### Example
**You can find helpful code snippets in various programming languages in the [examples](https://github.com/imgproxy/imgproxy/tree/master/examples) folder. There's a good chance you'll find a snippet in your favorite programming language that you'll be able to use right away.**
And here is a step-by-step example of a source URL encryption:
Before we start, we need an encryption key. We will use the AES-256-CBC algorithm in this example, so we need a 32-byte key. Let's assume we used a random generator and got the following hex-encoded key:
```
1eb5b0e971ad7f45324c1bb15c947cb207c43152fa5c6c7f35c4f36e0c18e0f1
```
Run imgproxy using this encryption key, like so:
```bash
IMGPROXY_SOURCE_URL_ENCRYPRION_KEY="1eb5b0e971ad7f45324c1bb15c947cb207c43152fa5c6c7f35c4f36e0c18e0f1" imgproxy
```
Next, assume that you have the following source URL:
```
http://example.com/images/curiosity.jpg
```
It's 39-byte long, so we should align it to 16 bytes using the PKCS #7 method:
```
http://example.com/images/curiosity.jpg\09\09\09\09\09\09\09\09\09
```
**📝Note:** From this point on, we'll show unprintable characters in `\NN` format where `NN` is a hex representation of the byte.
Next, we need an initialization vector (IV). Let's assume we generated the following IV:
```
\A7\95\63\A2\B3\5D\86\CE\E6\45\1C\3C\80\0F\53\5A
```
We'll use our encription key and IV encrypt a 16-byte-aligned source URL with the AES-256-CBC algorithm:
```
\84\65\19\C8\B7\97\59\2E\CE\A3\78\DD\44\25\45\A4\48\43\4A\AD\04\A5\B7\A8\50\01\22\CC\7E\65\1C\FF\71\57\3C\89\54\D8\6E\1B\0D\B3\13\41\2F\50\47\69
```
Add the IV to the beginning:
```
\A7\95\63\A2\B3\5D\86\CE\E6\45\1C\3C\80\0F\53\5A\84\65\19\C8\B7\97\59\2E\CE\A3\78\DD\44\25\45\A4\48\43\4A\AD\04\A5\B7\A8\50\01\22\CC\7E\65\1C\FF\71\57\3C\89\54\D8\6E\1B\0D\B3\13\41\2F\50\47\69
```
And finally, encode the result with URL-safe Base64:
```
p5VjorNdhs7mRRw8gA9TWoRlGci3l1kuzqN43UQlRaRIQ0qtBKW3qFABIsx-ZRz_cVc8iVTYbhsNsxNBL1BHaQ
```
Now you can put this encrypted URL in the imgproxy URL path, prepending it with the `/enc/` segment:
```
/unsafe/rs:fit:300:300/enc/p5VjorNdhs7mRRw8gA9TWoRlGci3l1kuzqN43UQlRaRIQ0qtBKW3qFABIsx-ZRz_cVc8iVTYbhsNsxNBL1BHaQ
```
**📝Note:** The imgproxy URL in this example is not signed but signing URLs is especially important when using encrypted source URLs to prevent a padding oracle attack.

View File

@ -720,9 +720,6 @@ Read more about presets in the [Presets](presets.md) guide.
Default: empty
## Source URL
There are two ways to specify the source url:
### Plain
The source URL can be provided as is, prepended by the `/plain/` segment:
@ -753,6 +750,20 @@ When using an encoded source URL, you can specify the [extension](#extension) af
/aHR0cDovL2V4YW1w/bGUuY29tL2ltYWdl/cy9jdXJpb3NpdHku/anBn.png
```
#### Encrypted with AES-CBC
The source URL can be encrypted with the AES-CBC algorithm, prepended by the `/enc/` segment. The encrypted URL can be split with `/` as desired:
```
/enc/jwV3wUD9r4VBIzgv/ang3Hbh0vPpcm5cc/VO5rHxzonpvZjppG/2VhDnX2aariBYegH/jlhw_-dqjXDMm4af/ZDU6y5sBog
```
When using an encrypted source URL, you can specify the [extension](#extension) after `.`:
```
/enc/jwV3wUD9r4VBIzgv/ang3Hbh0vPpcm5cc/VO5rHxzonpvZjppG/2VhDnX2aariBYegH/jlhw_-dqjXDMm4af/ZDU6y5sBog.png
```
## Extension
Extension specifies the format of the resulting image. Read more about image formats support [here](image_formats_support.md).

View File

@ -18,9 +18,6 @@ A signature protects your URL from being modified by an attacker. It is highly r
Once you set up your [URL signature](configuration.md#url-signature), check out the [Signing the URL](signing_the_url.md) guide to learn about how to sign your URLs. Otherwise, since the signature is required, feel free to use any string here.
### Source URL
There are two ways to specify source url:
#### Plain
The source URL can be provided as is, prepended by `/plain/` part:
@ -39,6 +36,14 @@ The source URL can be encoded with URL-safe Base64. The encoded URL can be split
/aHR0cDovL2V4YW1w/bGUuY29tL2ltYWdl/cy9jdXJpb3NpdHku/anBn
```
#### Encrypted with AES-CBC
The source URL can be encrypted with the AES-CBC algorithm, prepended by the `/enc/` segment. The encrypted URL can be split with `/` as desired:
```
/enc/jwV3wUD9r4VBIzgv/ang3Hbh0vPpcm5cc/VO5rHxzonpvZjppG/2VhDnX2aariBYegH/jlhw_-dqjXDMm4af/ZDU6y5sBog
```
## Response format
imgproxy responses with a JSON body and returns the following info:

View File

@ -0,0 +1,64 @@
//go:build exclude
// +build exclude
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"encoding/hex"
"fmt"
"io"
"log"
)
func pkcs7pad(data []byte, blockSize int) []byte {
padLen := blockSize - len(data)%blockSize
padding := bytes.Repeat([]byte{byte(padLen)}, padLen)
return append(data, padding...)
}
func main() {
key := "1eb5b0e971ad7f45324c1bb15c947cb207c43152fa5c6c7f35c4f36e0c18e0f1"
var (
keyBin []byte
err error
)
if keyBin, err = hex.DecodeString(key); err != nil {
log.Fatal("Key expected to be hex-encoded string")
}
url := "http://img.example.com/pretty/image.jpg"
c, err := aes.NewCipher(keyBin)
if err != nil {
log.Fatal(err)
}
data := pkcs7pad([]byte(url), aes.BlockSize)
ciphertext := make([]byte, aes.BlockSize+len(data))
iv := ciphertext[:aes.BlockSize]
// We use a random iv generation, but you'll probably want to use some
// deterministic method
if _, err = io.ReadFull(rand.Reader, iv); err != nil {
log.Fatal(err)
}
mode := cipher.NewCBCEncrypter(c, iv)
mode.CryptBlocks(ciphertext[aes.BlockSize:], data)
encryptedURL := base64.RawURLEncoding.EncodeToString(ciphertext)
// We don't sign the URL in this example but it is highly recommended to sign
// imgproxy URLs when imgproxy is being used in production.
// Signing URLs is especially important when using encrypted source URLs to
// prevent a padding oracle attack
fmt.Printf("/unsafe/rs:fit:300:300/enc/%s.jpg", encryptedURL)
}

View File

@ -0,0 +1,34 @@
const crypto = require('crypto');
const KEY = Buffer.from(
'1eb5b0e971ad7f45324c1bb15c947cb207c43152fa5c6c7f35c4f36e0c18e0f1',
'hex',
)
const encrypt = (target, key) => {
const data = Buffer.from(target).toString('binary');
// We use a random iv generation, but you'll probably want to use some
// deterministic method
const iv = crypto.randomBytes(16)
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
let encrypted = Buffer.from(
cipher.update(data, 'utf8', 'binary') + cipher.final('binary'),
'binary',
);
return Buffer.concat([iv, encrypted]).toString('base64').replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_')
}
const url = 'http://img.example.com/pretty/image.jpg'
const encrypted_url = encrypt(url, KEY)
// We don't sign the URL in this example but it is highly recommended to sign
// imgproxy URLs when imgproxy is being used in production.
// Signing URLs is especially important when using encrypted source URLs to
// prevent a padding oracle attack
const path = `/unsafe/rs:fit:300:300/enc/${encrypted_url}.jpg`
console.log(path)

View File

@ -0,0 +1,25 @@
require "openssl"
require "base64"
key = ["1eb5b0e971ad7f45324c1bb15c947cb207c43152fa5c6c7f35c4f36e0c18e0f1"].pack("H*")
url = "http://img.example.com/pretty/image.jpg"
# The key is 32 bytes long, so we use AES-256-CBC
cipher = OpenSSL::Cipher::AES.new(256, :CBC)
cipher.encrypt
# We use a random iv generation, but you'll probably want to use some
# deterministic method
iv = cipher.random_iv
cipher.key = key
cipher.iv = iv
encrypted_url = Base64.urlsafe_encode64(iv + cipher.update(url) + cipher.final).tr("=", "")
# We don't sign the URL in this example but it is highly recommended to sign
# imgproxy URLs when imgproxy is being used in production.
# Signing URLs is especially important when using encrypted source URLs to
# prevent a padding oracle attack
path = "/unsafe/rs:fit:300:300/enc/#{encrypted_url}.jpg"

View File

@ -1,3 +1,6 @@
//go:build exclude
// +build exclude
package main
import (