diff --git a/hiboo/captcha/captcha.py b/hiboo/captcha/captcha.py
index 2deec4c3d8fa29b14eeb6ffcb5f66e375312f141..8f10be8ee0221916ab120b5a749601a883dff24d 100644
--- a/hiboo/captcha/captcha.py
+++ b/hiboo/captcha/captcha.py
@@ -2,6 +2,10 @@ from wtforms import widgets
 from hiboo.captcha import fields
 
 import random
+import string
+import io
+import base64
+from PIL import Image, ImageDraw, ImageFont
 
 
 class DummyCaptchaField(fields.CaptchaField):
@@ -35,3 +39,57 @@ class DummyCaptchaField(fields.CaptchaField):
             field.data.isdigit() and
             challenge == int(field.data)
         )
+
+
+class GeneratedTextImageCaptchaField(fields.CaptchaField):
+    """ Generate a random text and build a fuzzy image based on that text.
+
+    This is the most common historical captcha type, although pretty easily
+    defeated by OCR nowadays.
+    """
+
+    widget = widgets.TextInput()
+    charset = string.ascii_uppercase + string.digits
+
+    def _value(self):
+        return ""
+
+    def challenge(self):
+        self.label = "Please copy the following text: "
+        length = random.randint(5,8)
+        return "".join(random.choice(self.charset) for _ in range(length))
+
+    def check(self, challenge, form, field):
+        return challenge.lower() == field.data.lower()
+
+    def make_image(self, text):
+        font = ImageFont.truetype("captcha.ttf", 14)
+        size = font.getsize(text)
+        size = (size[0] * 2, int(size[1] * 1.4))
+        image = Image.new("RGB", size, "#ffffff")
+        xpos = 2
+        for char in text:
+            drawn = " {} ".format(char)
+            fgimage = Image.new("RGB", size, "#000000")
+            charimage = Image.new("L", font.getsize(drawn), "#000000")
+            chardraw = ImageDraw.Draw(charimage)
+            chardraw.text((0, 0), drawn, font=font, fill="#ffffff")
+            charimage = charimage.rotate(random.randrange(-15,15), expand=0, resample=Image.BICUBIC)
+            charimage = charimage.crop(charimage.getbbox())
+            maskimage = Image.new("L", size)
+            maskimage.paste(charimage, (xpos, 4, xpos + charimage.size[0], 4 + charimage.size[1]))
+            size = maskimage.size
+            image = Image.composite(fgimage, image, maskimage)
+            xpos = xpos + 2 + charimage.size[0]
+        image = image.crop((0, 0, xpos + 1, size[1]))
+        return image
+
+    def __call__(self, **kwargs):
+        image = self.make_image(self.context["challenge"])
+        image_png = io.BytesIO()
+        image.save(image_png, "PNG")
+        image_string = base64.b64encode(image_png.getvalue()).decode("ascii")
+        return widgets.HTMLString(
+            '<img src="data:image/png;base64,{}">'.format(image_string) +
+            super(GeneratedTextImageCaptchaField, self).__call__(**kwargs)
+        )
diff --git a/hiboo/captcha/forms.py b/hiboo/captcha/forms.py
index ab1b71e24108b2db8375939364a13dc5cd9ffa66..41aadbbd9b47e068261dd642fd4e506987ca1078 100644
--- a/hiboo/captcha/forms.py
+++ b/hiboo/captcha/forms.py
@@ -6,5 +6,5 @@ import flask_wtf
 
 class DisplayForm(flask_wtf.FlaskForm):
     test = fields.StringField()
-    captcha = captcha.DummyCaptchaField()
+    captcha = captcha.GeneratedTextImageCaptchaField()
     submit = fields.SubmitField()