Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions src/captcha/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import copy
import wave
import struct
import random
import secrets
import operator
from functools import reduce

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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."""
Expand All @@ -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

Expand All @@ -234,15 +234,15 @@ 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:
voices: t.List[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)
Expand Down
50 changes: 25 additions & 25 deletions src/captcha/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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 = (
Expand Down Expand Up @@ -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))

Expand All @@ -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))
Expand All @@ -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)
Expand Down Expand Up @@ -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