събота, 29 март 2008 г.

Java Captcha

Стана ми интересно какви captcha-s има по света и колко са сигурни:)
Ето няколко примера:

1. *Лесни (и средно лесни) за декодиране








2. Трудни за декодиране








3. Още по трудни :)


- Чичо Гугъл (Google)




- Yahoo




- Hotmail



- ICQ





Като поогледах някои captchas стигнах до следните изводи (има няколко статии в които има по обстойно изброяване на необходимите условия един captcha да е добър, една от тях е дадена като линк по долу):
- хубаво е да не съдържат думи или поне от богат речник (например Facebook предлагат комбинация от 4 символа + дума или дума + 4 символа)
- фона да не контрастира много в смисъл да не са много отчетливи символите.
- самите символи да не са подредени на една линия и ако е възможно да са изкривени и завъртяни с някоя афинна трансформация по "случаен" принцип
- около и до самите символи да се доляпят всякакви елементи (точки, криви, фигурки, които да изкривяват самия символ)
- да се използват различни шрифтове за символите

От последната картинка с captcha na ICQ ми дойде на ум за фон да се използва някаква случайна плазма и така попаднах на един аплет за случайна фрактална плазма http://www.ic.sunysb.edu/Stu/jseyster/plasma/index.html
Кода който предлага Justin Seyster на http://www.ic.sunysb.edu/Stu/jseyster/plasma/source.html съм модифицирал минимално колкото да ми тръгне като Swing за експеимента. Оказа се че и това не е толкова лесно защото има един лек бъг в java.awt.Component.createImage(int width, int height) за приложения различни от аплет при стандартното извикване на метода с цел създаване на Image, метода връща null. За мое щастие след час два намерих решението - http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4066665
И всичко тръгна:)
Ето го крайния резултат:























package captcha;

/* Plasma.java *
* Written by Justin Seyster and released into public domain. *
* Generates a "plasma fractal" using random midpoint displacement. */

import java.applet.Applet;
import java.awt.*;
import java.awt.image.MemoryImageSource;

import javax.swing.JComponent;

public class Plasma extends JComponent//Applet
{

Image Buffer; //A buffer used to store the image
Graphics Context; //Used to draw to the buffer.

//Randomly displaces color value for midpoint depending on size
//of grid piece.
float Displace(float num)
{
float max = num / (float)(getSize().width + getSize().height) * 3;
return ((float)Math.random() - 0.5f) * max;
}

//Returns a color based on a color value, c.
Color ComputeColor(float c)
{
float Red = 0;
float Green = 0;
float Blue = 0;

if (c < red =" c" red =" (1.0f">= 0.3f && c < green =" (c" green =" (0.3f" green =" (1.3f">= 0.5f)
{
Blue = (c - 0.5f) * 2;
}
else
{
Blue = (0.5f - c) * 2;
}

return new Color(Red, Green, Blue);
}

//This is something of a "helper function" to create an initial grid
//before the recursive function is called.
void drawPlasma(Graphics g, int width, int height)
{
float c1, c2, c3, c4;

//Assign the four corners of the intial grid random color values
//These will end up being the colors of the four corners of the applet.
c1 = (float)Math.random();
c2 = (float)Math.random();
c3 = (float)Math.random();
c4 = (float)Math.random();

DivideGrid(g, 0, 0, width , height , c1, c2, c3, c4);
}

//This is the recursive function that implements the random midpoint
//displacement algorithm. It will call itself until the grid pieces
//become smaller than one pixel.
void DivideGrid(Graphics g, float x, float y, float width, float height, float c1, float c2, float c3, float c4)
{
float Edge1, Edge2, Edge3, Edge4, Middle;
float newWidth = width / 2;
float newHeight = height / 2;

if (width > 2 || height > 2)
{
Middle = (c1 + c2 + c3 + c4) / 4 + Displace(newWidth + newHeight); //Randomly displace the midpoint!
Edge1 = (c1 + c2) / 2; //Calculate the edges by averaging the two corners of each edge.
Edge2 = (c2 + c3) / 2;
Edge3 = (c3 + c4) / 2;
Edge4 = (c4 + c1) / 2;

//Make sure that the midpoint doesn't accidentally "randomly displaced" past the boundaries!
if (Middle < middle =" 0;"> 1.0f)
{
Middle = 1.0f;
}

//Do the operation over again for each of the four new grids.
DivideGrid(g, x, y, newWidth, newHeight, c1, Edge1, Middle, Edge4);
DivideGrid(g, x + newWidth, y, newWidth, newHeight, Edge1, c2, Edge2, Middle);
DivideGrid(g, x + newWidth, y + newHeight, newWidth, newHeight, Middle, Edge2, c3, Edge3);
DivideGrid(g, x, y + newHeight, newWidth, newHeight, Edge4, Middle, Edge3, c4);
}
else //This is the "base case," where each grid piece is less than the size of a pixel.
{
//The four corners of the grid piece will be averaged and drawn as a single pixel.
float c = (c1 + c2 + c3 + c4) / 4;

g.setColor(ComputeColor(c));
g.drawRect((int)x, (int)y, 1, 1); //Java doesn't have a function to draw a single pixel, so
//a 1 by 1 rectangle is used.
}
}

//Draw a new plasma fractal whenever the applet is clicked.
public boolean mouseUp(Event evt, int x, int y)
{
drawPlasma(Context, getSize().width, getSize().height);
repaint(); //Force the applet to draw the new plasma fractal.

return false;
}

//Whenever something temporarily obscures the applet, it must be redrawn manually.
//Since the fractal is stored in an offscreen buffer, this function only needs to
//draw the buffer to the screen again.
// public void paint(Graphics g)
// {
// g.drawImage(Buffer, 0, 0, this);
// //g.drawString("Hellooow World",20,40);
// }



public void paint(Graphics g)
{
update(g);
}

public void update(Graphics g) {

if( Buffer == null ) {

Buffer = createImage(1000,1000); //(100,100);

Context = Buffer.getGraphics();
drawPlasma(Context, getSize().width, getSize().height);
}

g.drawImage(Buffer, 0, 0, this);
}



public String getAppletInfo()
{
return "Plasma Fractal. Written January, 2002 by Justin Seyster.";
}

public void init()
{
this.setVisible(true);
// Buffer = createImage(10, 10); //Set up the graphics buffer and context.
// Context = Buffer.getGraphics();
// drawPlasma(Context, getSize().width, getSize().height); //Draw the first plasma fractal.
}
};

package captcha;

import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JFrame;

public class Main {

/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Plasma p = new Plasma();
p.setVisible(true);
p.init();

JFrame f = new JFrame("Plasma Effect");
f.getContentPane().add(p);

f.setSize(1000, 1000);
f.setLocation(100, 100);
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
f.setVisible(true);
}
}


Амин:)


Връзки / Links:
http://www.ic.sunysb.edu/Stu/jseyster/plasma/index.html
http://www.ic.sunysb.edu/Stu/jseyster/plasma/source.html

Безценното лекарство: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4066665
http://sam.zoy.org/pwntcha/
http://www.andrewtimberlake.com/blog/2006/06/developing-a-captcha-implementation-in-java.html
http://www.lafdc.com/captcha/


* Тук категоризацията - Лесни, Трудни, Още по трудни е относителна.