avatar Alex on Software
  • Blog
  • About

A card to remember

Jun 15, 2025
Title Logo

Once we decided that it would be nice to have something better than an ordinary “piece of paper with contacts”, something that would impress our customers and prevent them from throwing cards away so easily.

That’s how this project was started.

Our cards have become a kind of artifact since then, and people have definitely liked them, so we could say we succeeded.

Below, I’m going to explain the whole story and how it works. If you’re not a developer and/or don’t have enough technical skills, you would probably find this article too boring complicated.

Russian version of this article can be found in our company’s blog.

A long time ago in a galaxy far, far away:

It is a very famous challenge in the Computer Graphics field that started on May 4th, 1984 via a post on comp.graphics by Paul Heckbert ( More about this in his article “A Minimal Ray Tracer” from the book Graphics Gems IV).The goal was to produce the source code for a raytracer…that would fit on the back of a business card.

Some years ago I found this famous article and decided that want something similar, but related to my area of expertise.

I put on the back of our business card a small executable, self-compiling, self-encrypted application written in Java that starts an embedded HTTP server and then directs the browser to a static page with my contact details.

As you can see, there are only 18 lines of code and whole source fits business card dimensions:

How it looks in action on FreeBSD 14:

Yep, this is Solaris (OpenIndiana) running our vcard project:

Runs on any Linux/BSD/Unix with any JDK 1.8+, doesn’t matter locally or on server.

How to check

Copy this code into your favorite editor:

#!/bin/sh
t=$(mktemp -d);e=$(realpath  $0);sed '1,4d' $0|sed -e 's/p /public /g ; s/i /import /g' \
 -e 's/j\./java./g ; s/E!/Exception/g ; s/U8!/UTF-8/g ; s/RE?/ResponseHeaders/g'>$t/Yo.java
cd $t;javac -XDignore.symbol.file -cp . Yo.java && java -cp . Yo $e;exit 0
i com.sun.net.httpserver.*;i j.awt.Desktop;i j.io.*;i j.util.*;i j.net.*;i j.nio.file.*;
i java.security.MessageDigest;i javax.crypto.Cipher;i javax.crypto.spec.SecretKeySpec;
p class Yo{p static void main(String[]args)throws E!{Cipher dcipher=Cipher.getInstance("AES");
dcipher.init(2,new SecretKeySpec(Arrays.copyOf(MessageDigest.getInstance("SHA-1").digest(
new String(Files.readAllBytes(Paths.get(args[0]))).replaceFirst("ED=\"([^<]*)\";","")
.trim().getBytes()),16),"AES"));String ddata=new String(dcipher.doFinal(Base64.getDecoder()
.decode(ED)),"U8!");int p=8000;String h="0x7f000001";HttpServer s=HttpServer.create()
.createContext("/",(HttpExchange t)->{String r=String.format(ddata,System.nanoTime());t.getRE?()
.set("Content-type","text/plain;charset=U8!");t.sendRE?(200,r.getBytes("U8!").length);
try(OutputStream os=t.getResponseBody()){os.write(r.getBytes("U8!"));}}).getServer();
s.bind(new InetSocketAddress(p),1);new Timer().schedule(new TimerTask(){public void run(){
if(!Desktop.isDesktopSupported()){System.out.println(ddata);return;}try{Desktop.getDesktop()
.browse(new URI("http://"+h+":"+p));}catch(E! e){throw new RuntimeE!(e);}}},2000);s.start();}
static String ED="TfPrCIlXEUInGJPpr4++hQfa2Whq4RFzdbFP5C4s/s8=";}

Pay attention to spaces, empty lines, and line wrap; you need to copy the exact ‘as-is’. Otherwise, it will not work due to self-checking code consistency (see below).

Save as vcard.sh and make it executable:

 chmod +x ./vcard.sh

Check that you have any JDK 1.8+ installed and available on PATH:

java -version
javac -version

Run:

./vcard.sh

How it work

If you’ve read through this line, you’re probably interested in implementation—how this code actually works. Well, I’m explain.

There are actually two major parts:

a shell script in the header and a main code block in Java.

Let start from header:

!/bin/sh
t=$(mktemp -d);e=$(realpath  $0);sed '1,4d' $0|sed -e 's/p /public /g ; s/i /import /g' \
 -e 's/j\./java./g ; s/E!/Exception/g ; s/U8!/UTF-8/g ; s/RE?/ResponseHeaders/g'>$t/Yo.java
cd $t;javac -XDignore.symbol.file -cp . Yo.java && java -cp . Yo $e;exit 0

First line is a common UNIX shebang, used to start shell interpreter:

#!/bin/sh

Next line creates temp folder and stores its name into variable ‘t’:

t=$(mktemp -d);

Then the script reads the full path with the file name and stores it into variable ‘e’:

e=$(realpath  $0);

The next line reads script source but skips the first 4 lines (to extract the Java block):

sed '1,4d' $0

Then the script makes a lot of substitutions with sed to “deobfuscate” the Java source:

|sed -e 's/p /public /g ; s/i /import /g' \
 -e 's/j\./java./g ; s/E!/Exception/g ; s/U8!/UTF-8/g ; s/RE?/ResponseHeaders/g'>$t/Yo.java

Then the script calls the Java compiler:

javac -XDignore.symbol.file -cp . Yo.java

The exotic -XDignore.symbol.file key is required to hide warnings about com.sun.* class usage.

On the last step, the script starts the Java application compiled earlier:

java -cp . Yo $e

Now lets observe cleaned and formatted Java code:

import com.sun.net.httpserver.*;
import java.awt.Desktop;
import java.io.*;
import java.util.*;
import java.net.*;
import java.nio.file.*;
import java.security.MessageDigest;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class Yo {

 public static void main(String[] args) throws Exception {

    Cipher dcipher = Cipher.getInstance("AES");
    dcipher.init(2, 
    new SecretKeySpec(Arrays.copyOf(
    MessageDigest.getInstance("SHA-1").digest(
            new String(Files.readAllBytes(Paths.get(args[0])))
            .replaceFirst("ED=\"([^<]*)\";", "")
                    .trim().getBytes()), 16), "AES"));

    String ddata = new String(dcipher.doFinal(Base64.getDecoder()
            .decode(ED)), "UTF-8");

    int p = 8000;
    String h = "0x7f000001";

    HttpServer s = HttpServer.create()
            .createContext("/", (HttpExchange t) -> {
                String r = String.format(ddata, System.nanoTime());
                t.getResponseHeaders()
                        .set("Content-type", "text/plain;charset=UTF-8");
                t.sendResponseHeaders(200, r.getBytes("UTF-8").length);
                try (OutputStream os = t.getResponseBody()) {
                    os.write(r.getBytes("UTF-8"));
                }
            }).getServer();

    s.bind(new InetSocketAddress(p), 1);

    new Timer().schedule(new TimerTask() {
        public void run() {
            if (!Desktop.isDesktopSupported()) {
                System.out.println(ddata);
                return;
            }
            try {
                Desktop.getDesktop()
                        .browse(new URI("http://" + h + ":" + p));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }, 2000);
    s.start();
 }
static String ED = "1AtzGU0uq7J7DHPdjdJJ5JJDiwQi8mElIDOjuRK0DEU=";
}

Hope you’ll find it much more readable than the original version ;)

This is just an ordinary Java class named Yo that has the common public static void main(String[]) {} function as its start point, similar to any other Java program.

I’ll skip explanation of the first block for now, because its used as consistency check:

 Cipher dcipher = Cipher.getInstance("AES");
        dcipher.init(2, 
        new SecretKeySpec(Arrays.copyOf(
        MessageDigest.getInstance("SHA-1").digest(
                new String(Files.readAllBytes(Paths.get(args[0])))
                .replaceFirst("ED=\"([^<]*)\";", "")
                        .trim().getBytes()), 16), "AES"));
   String ddata = new String(dcipher.doFinal(Base64.getDecoder()
            .decode(ED)), "UTF-8"); 

And I want to be sure that computer kids would not be able to inject something awful inside so easy.

Just warn that each attempt to tamper any part of source will produce exception:

Exception in thread "main" javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
    at java.base/com.sun.crypto.provider.CipherCore.unpad(CipherCore.java:981)
    at java.base/com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1062)
    at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:853)
    at java.base/com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
    at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2202)
    at Yo.main(Yo.java:6)

So here we load embedded HTTP server:

 HttpServer s = HttpServer.create()
                .createContext("/", (HttpExchange t) -> {
                    String r = String.format(ddata, System.nanoTime());
                    t.getResponseHeaders()
                            .set("Content-type", "text/plain;charset=UTF-8");
                    t.sendResponseHeaders(200, r.getBytes("UTF-8").length);
                    try (OutputStream os = t.getResponseBody()) {
                        os.write(r.getBytes("UTF-8"));
                    }
                }).getServer();
  s.bind(new InetSocketAddress(p), 1);

You probably don’t know (this is not common knowledge), but each JDK from 1.8 contains an embedded HTTP server that could be started without any external libraries.

Point attention to this port and host definition:

 int p = 8000;
 String h = "0x7f000001";

Bit weird but supported notation to write IP-address. One of.

Next, we start “old good timer,” which just waits 2 seconds and then opens the browser (in another thread) and points to the server URL:

new Timer().schedule(new TimerTask() {
            public void run() {
                if (!Desktop.isDesktopSupported()) {
                    System.out.println(ddata);
                    return;
                }
                try {
                    Desktop.getDesktop()
                            .browse(new URI("http://" + h + ":" + p));
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }, 2000); 

At the very end we start our embedded server:

 s.start(); 

The last line in source:

 static String ED = "1AtzGU0uq7J7DHPdjdJJ5JJDiwQi8mElIDOjuRK0DEU=";

just contains my contacts and headline, which is displayed on page.

Good luck to tamper ;)