← Cloudflare Workers / workers / examples
Использование timingSafeEqual
Если вы хотите быстро начать, нажмите на кнопку ниже.
Это создаёт репозиторий в вашем аккаунте 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;