From 6e26be4f0b6f2a243c54ec7d7386d66e7323fcc0 Mon Sep 17 00:00:00 2001 From: Mahdi Bahreini Date: Tue, 25 Feb 2025 14:07:41 +0000 Subject: [PATCH] fix: replace secrets.randint with secure alternatives --- src/captcha/audio.py | 24 ++++++++++----------- src/captcha/image.py | 50 ++++++++++++++++++++++---------------------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/captcha/audio.py b/src/captcha/audio.py index 9a608ec..0a217dd 100644 --- a/src/captcha/audio.py +++ b/src/captcha/audio.py @@ -13,7 +13,7 @@ import copy import wave import struct -import random +import secrets import operator from functools import reduce @@ -84,7 +84,7 @@ def create_noise(length: int, level: int = 4) -> bytearray: adjust = 128 - int(level / 2) i = 0 while i < length: - v = random.randint(0, 256) + v = secrets.randbelow(257) noise[i] = v % level + adjust i += 1 return noise @@ -186,7 +186,7 @@ def random(self, length: int = 6) -> t.List[str]: :param length: the return string length. """ - return random.sample(self.choices, length) + return [secrets.choice(self.choices) for _ in range(length)] def load(self) -> None: """Load voice data into memory.""" @@ -203,27 +203,27 @@ def _load_data(self, name: str) -> None: self._cache[name] = data def _twist_pick(self, key: str) -> bytearray: - voice = random.choice(self._cache[key]) + voice = secrets.choice(self._cache[key]) # random change speed - speed = random.randrange(90, 120) / 100.0 + speed = (secrets.randbelow(31) + 90) / 100.0 voice = change_speed(voice, speed) # random change sound - level = random.randrange(80, 120) / 100.0 + level = (secrets.randbelow(41) + 80) / 100.0 voice = change_sound(voice, level) return voice def _noise_pick(self) -> bytearray: - key = random.choice(self.choices) - voice = random.choice(self._cache[key]) + key = secrets.choice(self.choices) + voice = secrets.choice(self._cache[key]) voice = copy.copy(voice) voice.reverse() - speed = random.randrange(8, 16) / 10.0 + speed = (secrets.randbelow(9) + 8) / 10.0 voice = change_speed(voice, speed) - level = random.randrange(2, 6) / 10.0 + level = (secrets.randbelow(5) + 2) / 10.0 voice = change_sound(voice, level) return voice @@ -234,7 +234,7 @@ def create_background_noise(self, length: int, chars: str) -> bytearray: sound = self._noise_pick() end = pos + len(sound) + 1 noise[pos:end] = mix_wave(sound, noise[pos:end]) - pos = end + random.randint(0, int(WAVE_SAMPLE_RATE / 10)) + pos = end + secrets.randbelow(int(WAVE_SAMPLE_RATE / 10) + 1) return noise def create_wave_body(self, chars: str) -> bytearray: @@ -242,7 +242,7 @@ def create_wave_body(self, chars: str) -> bytearray: inters: t.List[int] = [] for c in chars: voices.append(self._twist_pick(c)) - i = random.randint(WAVE_SAMPLE_RATE, WAVE_SAMPLE_RATE * 3) + i = secrets.randbelow(WAVE_SAMPLE_RATE * 3 - WAVE_SAMPLE_RATE + 1) + WAVE_SAMPLE_RATE inters.append(i) durations = map(lambda a: len(a), voices) diff --git a/src/captcha/image.py b/src/captcha/image.py index cd240a6..2fdcb16 100644 --- a/src/captcha/image.py +++ b/src/captcha/image.py @@ -8,7 +8,7 @@ from __future__ import annotations import os -import random +import secrets import typing as t from PIL.Image import new as createImage, Image, Transform, Resampling from PIL.ImageDraw import Draw, ImageDraw @@ -80,13 +80,13 @@ def truefonts(self) -> list[FreeTypeFont]: @staticmethod def create_noise_curve(image: Image, color: ColorTuple) -> Image: w, h = image.size - x1 = random.randint(0, int(w / 5)) - x2 = random.randint(w - int(w / 5), w) - y1 = random.randint(int(h / 5), h - int(h / 5)) - y2 = random.randint(y1, h - int(h / 5)) + x1 = secrets.randbelow(int(w / 5) + 1) + x2 = secrets.randbelow(w - int(w / 5) + 1) + int(w / 5) + y1 = secrets.randbelow(h - 2 * int(h / 5) + 1) + int(h / 5) + y2 = secrets.randbelow(h - y1 - int(h / 5) + 1) + y1 points = [x1, y1, x2, y2] - end = random.randint(160, 200) - start = random.randint(0, 20) + end = secrets.randbelow(41) + 160 + start = secrets.randbelow(21) Draw(image).arc(points, start, end, fill=color) return image @@ -99,8 +99,8 @@ def create_noise_dots( draw = Draw(image) w, h = image.size while number: - x1 = random.randint(0, w) - y1 = random.randint(0, h) + x1 = secrets.randbelow(w + 1) + y1 = secrets.randbelow(h + 1) draw.line(((x1, y1), (x1 - 1, y1 - 1)), fill=color, width=width) number -= 1 return image @@ -110,29 +110,29 @@ def _draw_character( c: str, draw: ImageDraw, color: ColorTuple) -> Image: - font = random.choice(self.truefonts) + font = secrets.choice(self.truefonts) _, _, w, h = draw.multiline_textbbox((1, 1), c, font=font) - dx1 = random.randint(*self.character_offset_dx) - dy1 = random.randint(*self.character_offset_dy) + dx1 = secrets.randbelow(self.character_offset_dx[1] - self.character_offset_dx[0] + 1) + self.character_offset_dx[0] + dy1 = secrets.randbelow(self.character_offset_dy[1] - self.character_offset_dy[0] + 1) + self.character_offset_dy[0] im = createImage('RGBA', (int(w) + dx1, int(h) + dy1)) Draw(im).text((dx1, dy1), c, font=font, fill=color) # rotate im = im.crop(im.getbbox()) im = im.rotate( - random.uniform(*self.character_rotate), + self.character_rotate[0] + (secrets.randbits(32) / (2**32)) * (self.character_rotate[1] - self.character_rotate[0]), Resampling.BILINEAR, expand=True, ) # warp - dx2 = w * random.uniform(*self.character_warp_dx) - dy2 = h * random.uniform(*self.character_warp_dy) - x1 = int(random.uniform(-dx2, dx2)) - y1 = int(random.uniform(-dy2, dy2)) - x2 = int(random.uniform(-dx2, dx2)) - y2 = int(random.uniform(-dy2, dy2)) + dx2 = w * (secrets.randbits(32) / (2**32)) * (self.character_warp_dx[1] - self.character_warp_dx[0]) + self.character_warp_dx[0] + dy2 = h * (secrets.randbits(32) / (2**32)) * (self.character_warp_dy[1] - self.character_warp_dy[0]) + self.character_warp_dy[0] + x1 = int(secrets.randbits(32) / (2**32) * (dx2 - (-dx2)) + (-dx2)) + y1 = int(secrets.randbits(32) / (2**32) * (dy2 - (-dy2)) + (-dy2)) + x2 = int(secrets.randbits(32) / (2**32) * (dx2 - (-dx2)) + (-dx2)) + y2 = int(secrets.randbits(32) / (2**32) * (dy2 - (-dy2)) + (-dy2)) w2 = w + abs(x1) + abs(x2) h2 = h + abs(y1) + abs(y2) data = ( @@ -163,7 +163,7 @@ def create_captcha_image( images: list[Image] = [] for c in chars: - if random.random() > self.word_space_probability: + if secrets.randbits(32) / (2**32) > self.word_space_probability: images.append(self._draw_character(" ", draw, color)) images.append(self._draw_character(c, draw, color)) @@ -180,7 +180,7 @@ def create_captcha_image( w, h = im.size mask = im.convert('L').point(self.lookup_table) image.paste(im, (offset, int((self._height - h) / 2)), mask) - offset = offset + w + random.randint(-rand, 0) + offset = offset + w + (-secrets.randbelow(rand + 1)) if width > self._width: image = image.resize((self._width, self._height)) @@ -197,7 +197,7 @@ def generate_image(self, chars: str, :param fg_color: foreground color of the text in rgba format (r,g,b,a). """ background = bg_color if bg_color else random_color(238, 255) - random_fg_color = random_color(10, 200, random.randint(220, 255)) + random_fg_color = random_color(10, 200, secrets.randbelow(36) + 220) color: ColorTuple = fg_color if fg_color else random_fg_color im = self.create_captcha_image(chars, color, background) @@ -241,9 +241,9 @@ def random_color( start: int, end: int, opacity: int | None = None) -> ColorTuple: - red = random.randint(start, end) - green = random.randint(start, end) - blue = random.randint(start, end) + red = secrets.randbelow(end - start + 1) + start + green = secrets.randbelow(end - start + 1) + start + blue = secrets.randbelow(end - start + 1) + start if opacity is None: return red, green, blue return red, green, blue, opacity