INTEGRITY Документация

Использование timingSafeEqual

Если вы хотите быстро начать, нажмите на кнопку ниже.

Deploy to Cloudflare

Это создаёт репозиторий в вашем аккаунте GitHub и разворачивает приложение в Cloudflare Workers.

crypto.subtle.timingSafeEqual функция сравнивает два значения с помощью алгоритма с постоянным временем выполнения. Затраченное время не зависит от содержимого значений.

При сравнении строк с помощью оператора равенства (== или ===), сравнение завершится на первом несовпадающем символе. Если использовать timingSafeEqual, злоумышленник не сможет использовать замеры времени, чтобы определить, в какой момент две строки начинают отличаться.

timingSafeEqual функция принимает два ArrayBuffer или TypedArray значения для сравнения. Эти буферы должны быть одинаковой длины, иначе будет выброшено исключение. Обратите внимание, что эта функция не является постоянной по времени относительно длины параметров и также не гарантирует постоянное время для окружающего кода. С секретами следует обращаться осторожно, чтобы не создавать временные побочные каналы.

Чтобы сравнить две строки, необходимо использовать TextEncoder API.

interface Environment {
	MY_SECRET_VALUE?: string;
}

export default {
	async fetch(req: Request, env: Environment) {
		if (!env.MY_SECRET_VALUE) {
			return new Response("Missing secret binding", { status: 500 });
		}

		const authToken = req.headers.get("Authorization") || "";

		const encoder = new TextEncoder();

		const userValue = encoder.encode(authToken);
		const secretValue = encoder.encode(env.MY_SECRET_VALUE);

		// Do not return early when lengths differ — that leaks the secret's
		// length through timing.  Instead, always perform a constant-time
		// comparison: when the lengths match compare directly; otherwise
		// compare the user input against itself (always true) and negate.
		const lengthsMatch = userValue.byteLength === secretValue.byteLength;
		const isEqual = lengthsMatch
			? crypto.subtle.timingSafeEqual(userValue, secretValue)
			: !crypto.subtle.timingSafeEqual(userValue, userValue);

		if (!isEqual) {
			return new Response("Unauthorized", { status: 401 });
		}

		return new Response("Welcome!");
	},
};
from workers import WorkerEntrypoint, Response
from js import TextEncoder, crypto

class Default(WorkerEntrypoint):
    async def fetch(self, request):
        auth_token = request.headers["Authorization"] or ""
        secret = self.env.MY_SECRET_VALUE

        if secret is None:
            return Response("Missing secret binding", status=500)

        encoder = TextEncoder.new()
        user_value = encoder.encode(auth_token)
        secret_value = encoder.encode(secret)

        # Do not return early when lengths differ — that leaks the secret's
        # length through timing.  Always perform a constant-time comparison.
        if user_value.byteLength == secret_value.byteLength:
            is_equal = crypto.subtle.timingSafeEqual(user_value, secret_value)
        else:
            is_equal = not crypto.subtle.timingSafeEqual(user_value, user_value)

        if not is_equal:
            return Response("Unauthorized", status=401)

        return Response("Welcome!")
import { Hono } from 'hono';

interface Environment {
  Bindings: {
    MY_SECRET_VALUE?: string;
  }
}

const app = new Hono<Environment>();

// Middleware to handle authentication with timing-safe comparison
app.use('*', async (c, next) => {
  const secret = c.env.MY_SECRET_VALUE;

  if (!secret) {
    return c.text("Missing secret binding", 500);
  }

  const authToken = c.req.header("Authorization") || "";

  const encoder = new TextEncoder();

  const userValue = encoder.encode(authToken);
  const secretValue = encoder.encode(secret);

  // Do not return early when lengths differ — that leaks the secret's
  // length through timing.  Instead, always perform a constant-time
  // comparison: when the lengths match compare directly; otherwise
  // compare the user input against itself (always true) and negate.
  const lengthsMatch = userValue.byteLength === secretValue.byteLength;
  const isEqual = lengthsMatch
    ? crypto.subtle.timingSafeEqual(userValue, secretValue)
    : !crypto.subtle.timingSafeEqual(userValue, userValue);

  if (!isEqual) {
    return c.text("Unauthorized", 401);
  }

  // If we got here, the auth token is valid
  await next();
});

// Protected route
app.get('*', (c) => {
  return c.text("Welcome!");
});

export default app;