Kryptera med nyckel, IV och HMAC och spara som sträng

Hjälpte en bekant med att implementera något liknande Microsoft VIEWSTATE i java, dvs konfigurerbar krypteringsnivå och HMAC för att stoppa klientsidemanipluering av data. Snyggt va?

Färgmärkt och indenterat!

/*
* Blaufish’s HMAC’ed and IV:ed encryptor/decryotor
*
* Some rights reserved;
*
*/
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.junit.Test;
public class TestIsATest {
private static final String HMAC_ALGORTIHM = ”HmacSHA1”;
@Test
public void test1() throws Exception {
SecureRandom rnd = new SecureRandom();
byte[] keybytes24 = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24 };
byte[] iv = new byte[8];
rnd.nextBytes(iv);
testEncryption(keybytes24, iv, ”DESEDE”, ”/CBC/PKCS5Padding”);
}
@Test
public void test2() throws Exception {
SecureRandom rnd = new SecureRandom();
byte[] keybytes24 = new byte[24];
rnd.nextBytes(keybytes24);
byte[] iv = new byte[8];
rnd.nextBytes(iv);
testEncryption(keybytes24, iv, ”DESEDE”, ”/CBC/PKCS5Padding”);
}
@Test
public void test3() throws Exception {
SecureRandom rnd = new SecureRandom();
byte[] keybytes8 = new byte[8];
rnd.nextBytes(keybytes8);
byte[] iv = new byte[8];
rnd.nextBytes(iv);
testEncryption(keybytes8, iv, ”DES”, ”/CBC/PKCS5Padding”);
}
@Test
public void test4() throws Exception {
SecureRandom rnd = new SecureRandom();
byte[] keybytes16 = new byte[16];
rnd.nextBytes(keybytes16);
byte[] iv = new byte[16];
rnd.nextBytes(iv);
testEncryption(keybytes16, iv, ”AES”, ”/CBC/PKCS5Padding”);
}
@Test
public void test5() throws Exception {
SecureRandom rnd = new SecureRandom();
byte[] keybytes16 = new byte[16];
rnd.nextBytes(keybytes16);
byte[] iv = new byte[8];
rnd.nextBytes(iv);
testEncryption(keybytes16, iv, ”blowfish”, ”/CBC/PKCS5Padding”);
}
static void testEncryption(byte[] keybytes, byte[] iv,
final String algorithm, String algorithmModifiers)
throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, IllegalBlockSizeException,
BadPaddingException, InvalidKeySpecException,
InvalidAlgorithmParameterException {
byte[] cleartext = ”This is just an example”.getBytes();
String encrypted = encrypt(keybytes, iv, cleartext, algorithm,
algorithmModifiers);
byte[] cleartext1 = decrypt(keybytes, encrypted, algorithm,
algorithmModifiers);
System.out.println(new String(cleartext1));
}
private static String encrypt(byte[] keybytes, byte[] iv, byte[] cleartext,
final String algorithm, String algorithmModifiers)
throws NoSuchAlgorithmException, InvalidKeyException,
NoSuchPaddingException, InvalidAlgorithmParameterException,
IllegalBlockSizeException, BadPaddingException {
SecretKey key = generateKey(keybytes, algorithm);
Mac mac = Mac.getInstance(HMAC_ALGORTIHM);
Cipher cipher;
cipher = Cipher.getInstance(algorithm + algorithmModifiers);
AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
byte[] ciphertext = cipher.doFinal(cleartext);
mac.init(key);
byte[] keyedDigest = mac.doFinal(cleartext);
TupleIvCiphertextMac icm = new TupleIvCiphertextMac(iv, ciphertext,
keyedDigest);
return icm.generateEncodedString();
}
private static byte[] decrypt(byte[] keybytes, String encryptedString,
final String algorithm, String algorithmModifiers)
throws NoSuchAlgorithmException, InvalidKeyException,
NoSuchPaddingException, InvalidAlgorithmParameterException,
IllegalBlockSizeException, BadPaddingException {
TupleIvCiphertextMac icm = new TupleIvCiphertextMac(encryptedString);
SecretKey key = generateKey(keybytes, algorithm);
Mac mac = Mac.getInstance(HMAC_ALGORTIHM);
Cipher cipher;
cipher = Cipher.getInstance(algorithm + algorithmModifiers);
AlgorithmParameterSpec paramSpec = new IvParameterSpec(icm.iv);
cipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
byte[] cleartext = cipher.doFinal(icm.ciphertext);
mac.init(key);
byte[] keyedDigest = mac.doFinal(cleartext);
if (!Arrays.equals(keyedDigest, icm.mac)) {
throw new RuntimeException(”MAC failure”);
}
return cleartext;
}
private static SecretKey generateKey(byte[] keybytes, String algorithm)
throws NoSuchAlgorithmException, InvalidKeyException {
SecretKey secretKey = new SecretKeySpec(keybytes, algorithm);
if (algorithm.contains(”DES”)) {
SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm);
secretKey = skf.translateKey(secretKey);
}
return secretKey;
}
private static class TupleIvCiphertextMac {
private static final char FIELD_SEPARATOR = ‘:’;
byte[] iv;
byte[] ciphertext;
byte[] mac;
TupleIvCiphertextMac(byte[] iv, byte[] ciphertext, byte[] mac) {
this.iv = iv;
this.ciphertext = ciphertext;
this.mac = mac;
}
String generateEncodedString() {
String sIV = Base64.encodeBase64String(iv);
String sCiphertext = Base64.encodeBase64String(ciphertext);
String sMac = Base64.encodeBase64String(mac);
StringBuilder sb = new StringBuilder().append(sIV).append(
FIELD_SEPARATOR).append(sCiphertext)
.append(FIELD_SEPARATOR).append(sMac);
return sb.toString();
}
TupleIvCiphertextMac(String encodedString) {
int separator1 = encodedString.indexOf(FIELD_SEPARATOR);
int separator2 = encodedString.lastIndexOf(FIELD_SEPARATOR);
if (separator1 == separator2) {
throw new IllegalArgumentException();
}
String sIV = encodedString.substring(0, separator1 – 1);
String sCiphertext = encodedString.substring(separator1 + 1,
separator2 – 1);
String sMac = encodedString.substring(separator2 + 1);
iv = Base64.decodeBase64(sIV);
ciphertext = Base64.decodeBase64(sCiphertext);
mac = Base64.decodeBase64(sMac);
}
}
}
/*
* Blaufish’s HMAC’ed and IV:ed encryptor/decryptor
*
* Some rights reserved;
*
*/
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.junit.Test;
public class TestIsATest {
private static final String HMAC_ALGORTIHM = ”HmacSHA1”;
@Test
public void test1() throws Exception {
SecureRandom rnd = new SecureRandom();
byte[] keybytes24 = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24 };
byte[] iv = new byte[8];
rnd.nextBytes(iv);
testEncryption(keybytes24, iv, ”DESEDE”, ”/CBC/PKCS5Padding”);
}
@Test
public void test2() throws Exception {
SecureRandom rnd = new SecureRandom();
byte[] keybytes24 = new byte[24];
rnd.nextBytes(keybytes24);
byte[] iv = new byte[8];
rnd.nextBytes(iv);
testEncryption(keybytes24, iv, ”DESEDE”, ”/CBC/PKCS5Padding”);
}
@Test
public void test3() throws Exception {
SecureRandom rnd = new SecureRandom();
byte[] keybytes8 = new byte[8];
rnd.nextBytes(keybytes8);
byte[] iv = new byte[8];
rnd.nextBytes(iv);
testEncryption(keybytes8, iv, ”DES”, ”/CBC/PKCS5Padding”);
}
@Test
public void test4() throws Exception {
SecureRandom rnd = new SecureRandom();
byte[] keybytes16 = new byte[16];
rnd.nextBytes(keybytes16);
byte[] iv = new byte[16];
rnd.nextBytes(iv);
testEncryption(keybytes16, iv, ”AES”, ”/CBC/PKCS5Padding”);
}
@Test
public void test5() throws Exception {
SecureRandom rnd = new SecureRandom();
byte[] keybytes16 = new byte[16];
rnd.nextBytes(keybytes16);
byte[] iv = new byte[8];
rnd.nextBytes(iv);
testEncryption(keybytes16, iv, ”blowfish”, ”/CBC/PKCS5Padding”);
}
static void testEncryption(byte[] keybytes, byte[] iv,
final String algorithm, String algorithmModifiers)
throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, IllegalBlockSizeException,
BadPaddingException, InvalidKeySpecException,
InvalidAlgorithmParameterException {
byte[] cleartext = ”This is just an example”.getBytes();
String encrypted = encrypt(keybytes, iv, cleartext, algorithm,
algorithmModifiers);
byte[] cleartext1 = decrypt(keybytes, encrypted, algorithm,
algorithmModifiers);
System.out.println(new String(cleartext1));
}
private static String encrypt(byte[] keybytes, byte[] iv, byte[] cleartext,
final String algorithm, String algorithmModifiers)
throws NoSuchAlgorithmException, InvalidKeyException,
NoSuchPaddingException, InvalidAlgorithmParameterException,
IllegalBlockSizeException, BadPaddingException {
SecretKey key = generateKey(keybytes, algorithm);
Mac mac = Mac.getInstance(HMAC_ALGORTIHM);
Cipher cipher;
cipher = Cipher.getInstance(algorithm + algorithmModifiers);
AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
byte[] ciphertext = cipher.doFinal(cleartext);
mac.init(key);
byte[] keyedDigest = mac.doFinal(cleartext);
TupleIvCiphertextMac icm = new TupleIvCiphertextMac(iv, ciphertext,
keyedDigest);
return icm.generateEncodedString();
}
private static byte[] decrypt(byte[] keybytes, String encryptedString,
final String algorithm, String algorithmModifiers)
throws NoSuchAlgorithmException, InvalidKeyException,
NoSuchPaddingException, InvalidAlgorithmParameterException,
IllegalBlockSizeException, BadPaddingException {
TupleIvCiphertextMac icm = new TupleIvCiphertextMac(encryptedString);
SecretKey key = generateKey(keybytes, algorithm);
Mac mac = Mac.getInstance(HMAC_ALGORTIHM);
Cipher cipher;
cipher = Cipher.getInstance(algorithm + algorithmModifiers);
AlgorithmParameterSpec paramSpec = new IvParameterSpec(icm.iv);
cipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
byte[] cleartext = cipher.doFinal(icm.ciphertext);
mac.init(key);
byte[] keyedDigest = mac.doFinal(cleartext);
if (!Arrays.equals(keyedDigest, icm.mac)) {
throw new RuntimeException(”MAC failure”);
}
return cleartext;
}
private static SecretKey generateKey(byte[] keybytes, String algorithm)
throws NoSuchAlgorithmException, InvalidKeyException {
SecretKey secretKey = new SecretKeySpec(keybytes, algorithm);
if (algorithm.contains(”DES”)) {
SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm);
secretKey = skf.translateKey(secretKey);
}
return secretKey;
}
private static class TupleIvCiphertextMac {
private static final char FIELD_SEPARATOR = ‘:’;
byte[] iv;
byte[] ciphertext;
byte[] mac;
TupleIvCiphertextMac(byte[] iv, byte[] ciphertext, byte[] mac) {
this.iv = iv;
this.ciphertext = ciphertext;
this.mac = mac;
}
String generateEncodedString() {
String sIV = Base64.encodeBase64String(iv);
String sCiphertext = Base64.encodeBase64String(ciphertext);
String sMac = Base64.encodeBase64String(mac);
StringBuilder sb = new StringBuilder().append(sIV).append(
FIELD_SEPARATOR).append(sCiphertext)
.append(FIELD_SEPARATOR).append(sMac);
return sb.toString();
}
TupleIvCiphertextMac(String encodedString) {
int separator1 = encodedString.indexOf(FIELD_SEPARATOR);
int separator2 = encodedString.lastIndexOf(FIELD_SEPARATOR);
if (separator1 == separator2) {
throw new IllegalArgumentException();
}
String sIV = encodedString.substring(0, separator1 – 1);
String sCiphertext = encodedString.substring(separator1 + 1,
separator2 – 1);
String sMac = encodedString.substring(separator2 + 1);
iv = Base64.decodeBase64(sIV);
ciphertext = Base64.decodeBase64(sCiphertext);
mac = Base64.decodeBase64(sMac);
}
}
}

Don’t drink and code!

Fredag klockan 00:30 började en berusad Blaufish skriva kod. Klockan två var koden klar, och fisken var något mindre onykter. Då började jag inse den enorma idiotin i följande:

<html>
before… <div id=”data”></div> after!
<script>
var html = ””;
function out(data) {
html = html + data + ” ”;
document.getElementById(‘data’).innerHTML = html;
}
function e(data) {
str = ””;
var i;
for(i = 0; i < data.length; i++) {
var c = data.charAt(i);
switch(c) {
case ‘<‘: str += ‘&lt;’; break;
case ‘>’: str += ‘&gt;’; break;
case ‘”‘: str += ‘&quot;’; break;
default:  str += c;
}
}
return str;
}
if (typeof DOMParser == ”undefined”) {
DOMParser = function () {}
DOMParser.prototype.parseFromString = function (str, contentType) {
if (typeof ActiveXObject != ”undefined”) {
var d = new ActiveXObject(”MSXML.DomDocument”);
d.loadXML(str);
return d;
} else if (typeof XMLHttpRequest != ”undefined”) {
var req = new XMLHttpRequest;
req.open(”GET”, ”data:” + (contentType || ”application/xml”) +
”;charset=utf-8,” + encodeURIComponent(str), false);
if (req.overrideMimeType) {
req.overrideMimeType(contentType);
}
req.send(null);
return req.responseXML;
}
}
}
function xhr_handler() {
//if(this.readyState == 4 && this.status == 200) {
if(this.readyState == 4) {
var xml =  (new DOMParser()).parseFromString(this.responseText, ”text/xml”);
rss_handler(xml);
}
}
function rss_handler(xml) {
var rss = xml.getElementsByTagName(‘rss’)[0];
var channels = rss.getElementsByTagName(‘channel’);
var channel;
var i;
for(i=0; i < channels.length; i++) {
channel = channels.item(i);
var channel_title = channel.getElementsByTagName(‘title’).item(0).firstChild.nodeValue;
var channel_link = channel.getElementsByTagName(‘link’).item(0).firstChild.nodeValue;
out(‘<a href=”‘ + channel_link + ‘”>’ + channel_title + ‘</a><br>’);
var items = channel.getElementsByTagName(‘item’);
var j;
for(j=0; j < items.length; j++) {
var items_item = items.item(j);
var items_title = items_item.getElementsByTagName(‘title’).item(0).firstChild.nodeValue;
out(e(items_title)+'<br>’);
}
}
}
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = xhr_handler;
xhr.open(”GET”, url);
xhr.setRequestHeader(”Cache-Control”, ”no-cache”);
xhr.send();
</script>
</html>
<html>
before… <div id=”data”></div> after!
<script>
var html = ””;
function out(data) {
html = html + data + ” ”;
document.getElementById(‘data’).innerHTML = html;
}
function e(data) {
str = ””;
var i;
for(i = 0; i < data.length; i++) {
var c = data.charAt(i);
switch(c) {
case ‘<‘: str += ‘&lt;’; break;
case ‘>’: str += ‘&gt;’; break;
case ‘”‘: str += ‘&quot;’; break;
default:  str += c;
}
}
return str;
}
if (typeof DOMParser == ”undefined”) {
DOMParser = function () {}
DOMParser.prototype.parseFromString = function (str, contentType) {
if (typeof ActiveXObject != ”undefined”) {
var d = new ActiveXObject(”MSXML.DomDocument”);
d.loadXML(str);
return d;
} else if (typeof XMLHttpRequest != ”undefined”) {
var req = new XMLHttpRequest;
req.open(”GET”, ”data:” + (contentType || ”application/xml”) +
”;charset=utf-8,” + encodeURIComponent(str), false);
if (req.overrideMimeType) {
req.overrideMimeType(contentType);
}
req.send(null);
return req.responseXML;
}
}
}
function xhr_handler() {
//if(this.readyState == 4 && this.status == 200) {
if(this.readyState == 4) {
var xml =  (new DOMParser()).parseFromString(this.responseText, ”text/xml”);
rss_handler(xml);
}
}
function rss_handler(xml) {
var rss = xml.getElementsByTagName(‘rss’)[0];
var channels = rss.getElementsByTagName(‘channel’);
var channel;
var i;
for(i=0; i < channels.length; i++) {
channel = channels.item(i);
var channel_title = channel.getElementsByTagName(‘title’).item(0).firstChild.nodeValue;
var channel_link = channel.getElementsByTagName(‘link’).item(0).firstChild.nodeValue;
out(‘<a href=”‘ + channel_link + ‘”>’ + channel_title + ‘</a><br>’);
var items = channel.getElementsByTagName(‘item’);
var j;
for(j=0; j < items.length; j++) {
var items_item = items.item(j);
var items_title = items_item.getElementsByTagName(‘title’).item(0).firstChild.nodeValue;
out(e(items_title)+'<br>’);
}
}
}
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = xhr_handler;
xhr.open(”GET”, url);
xhr.setRequestHeader(”Cache-Control”, ”no-cache”);
xhr.send();
</script>
</html>
1. Det är helt onödigt, det finns redan enklare sätt att få in twitter på sin blogg (typ, använda det på twitters hemsida)
2. Det verkar bara fungera i IE. I övriga browsers får man inte ens läsa HTTP response-koden, vilket förmodligen är ett säkerhetsskydd mot datastölder (förbud mot cross domain XHR).
Både 1 och 2 borde jag vetat från start. Men med en massa vin i sig så…
Mycket är av koden är lånat från webb, det mesta är exempelkod och annan public domain.
Intressanta läror:
IE populerar inte responseXML om innehållstypen är RSS.
IE exponerar inte xml-parsern per default.

iPhone 3GS recension till geocaching.com

OBVIOUS:
Cool phone which can do ”everything”. Great games and apps.
GEOCACHING, GOOD:
The compass is excellent, the geocaching software by groundspeak is also very good (limited, but good).
The A-GPS solution works superb, gets GPS lock almost instantly 9 times out of 10.
Can show geocache in Google maps, and google maps can route way to geocache in the city.
GEOCACING, BAD:
GPS reception is a bit lacking compared to e.g. Garmin Colorado, GPS 60 etc.
You need to use map and hint a lot as GPS coordinates often are too off to be used geocaching.
No wherigo support?
No good support for GPS avaraging and other ”how to hide a geocache”; definately something which beginners would need and appreciate, the phone should tell beginners what they need to do and if the coordinate is good enough.
No good support for making temporary notes, i.e. you need pen&paper to make notes during multi’s and mysterys.
Not suitable as a primary GPS, more like a secondary supporting gadjet – I use it mainly to log online.
Some issues can be remedied by using e.g. MotionX but in my opionon iPhone is clearly less powerfull than a Garmin device as soon as you need to do any moderately complex task. Better application support can remedy some of it in the future though.
OBVIOUS:
  • Cool phone which can do ”everything”.
  • Great games and apps.
GEOCACHING, GOOD:
  • The compass is excellent, the geocaching software by groundspeak is also very good (limited, but good).
  • The A-GPS solution works superb, gets GPS lock almost instantly 9 times out of 10.
  • Can show geocache in Google maps, and google maps can route way to geocache in the city.
GEOCACING, BAD:
  • GPS reception is a bit lacking compared to e.g. Garmin Colorado, GPS 60 etc.
  • You need to use map and hint a lot as GPS coordinates often are too off to be used geocaching.
  • No wherigo support?
  • No good support for GPS avaraging and other ”how to hide a geocache”; definately something which beginners would need and appreciate, the phone should tell beginners what they need to do and if the coordinate is good enough.
  • No good support for making temporary notes, i.e. you need pen&paper to make notes during multi’s and mysterys.
  • Not suitable as a primary GPS, more like a secondary supporting gadjet – I use it mainly to log online.
Some issues can be remedied by using e.g. MotionX but in my opionon iPhone is clearly less powerfull than a Garmin device as soon as you need to do any moderately complex task. Better application support can remedy some of it in the future though.

(är vad jag tänkte skriva, men fick korta ner något för att komma in under gc.com’s begränsade maxlängd)

GO:TEBORG Wherigo

Jag och Benrangel gav oss ut i rusket för att pröva Göteborgs nya Wherigo cache, GO:TEBORG, stadens första. Jag har kört Wherigo lite grann, mest ”Build your own GPSr”, men detta var första gången det varit en cache kopplad till Wherigo-programmet. I grova drag var det muti på sightseeing tema med lite bilder och turistinformation. Ett ganska trevlit och kul upplägg. Ser fram emot att lite mer spel-orienterade wherigo-cacher publiceras, kan bli riktigt kul!


October 11 by blaufish (1338 found)
Ombedd att ge feedback!

Wherigo enhet/mjukvara: Garmin Colorado 300

Erfarenheter/synpunkter:

– Helhetintryck av tekniken: funkade klockrent. Inget strul, bara funkade!

– Potentiell tekniskt förbättring: vore fint om man bedömdes framme när man var inom 3 meter från en waypoint. Lite lustigt att jaga den exakta nollan när man är framme, tog typ 1-2 min att jaga den vid en punkt (gissa om jag såg kul ut om någon spanat på mig just då), annars så var det oftast pang på.

– Cacheupplägg: ganska trevlig promenad en ganska blöt, kall och rå dag, lite lång så det var kul att ha benrangel med så man slapp traska runt ensam i någon timme. Kändes ungefär som en bra och enklare multi, vilket är helt OK eftersom det var just det jag förväntade mig från cachebeskrivningen. Jag tror wherigo systemet tillåter mer avancerade cacher, t.ex. att waypoints dyker upp allt eftersom, RPG-liknande spel osv. Tycker cachen är finfin och inte bör ändras så mycket, men denna lämnar mersmak – Göteborg bör få lite fler och mer exotiska wherigo’s.

Tack för en bra start j-annie 🙂


October 11 by blaufish (1338 found)
Min första wherigo ! med benrangel. Ganska kul faktiskt värt att börja experimentera med detta!



Sen ströcachade vi lite. På det hela taget väldigt skönt att få vara ute och röra rejält på kroppen igen, och prata om både geocaching och de stora och små frågorna i livet. Båda var rätt trötta och slitna av lite olika anledningar så vi var helt i synk: stopp på Max, McD och cafér höll oss vid liv och värmde upp oss när vi var lite för frusna från det blöta. Någon sådan här tur behövs för att få vila hjärnan, fräsha upp kroppen och få nya krafter ibland!

På väg mot Feskekrykan träffade vi förresten två glada nybörjare som såg att vi bar GPS:er och undrade om vi hittat cachen. Lite förvirring senare insåg vi att de referade till Petit och förklarade att vi inte letat efter den men pekade ut var den skall sitta och inflikade ”kanske inte så lämplig att ta nu när det är blött och halt”. De berättade att de loggat 18 cacher och jag viftade glatt åt benrangel och frågade ”Benrangel, hur många har du tatt nu?” ”Drygt 2500”. De hoppade till lite förskräckt =) Tyvärr visade de sig vara bilburna och ha en egen plan, annars hade vi nog erbjudit dem att haka på vår wherigo-färd.

Intensivt…

Väldigt mycket just nu, därav att jag inte bloggat. Försökt twittra lite från mobilen istället, så att det skall komma lite linjebrus från mig åtminstone! La just till ett litet twitter widget på startsidan. Dock så verkar det bara funka i IE och Chrome, i FF krånglar det (varken adblockern eller noscript spyr på det dock, vet det tusan vad det kan vara.

På cachingfronten så loggade jag just Three Wise Monkeys som varit ett problem länge men som jag och Benrangel fick löst för några dagar sen.
Annars så har jag satt loggat några trevliga legacy-cacher; så som Red Ghost Letterbox (Letterbox Hybrid) och Keep an eye on the road (Webcam Cache). Speciellt Red Ghost var kul, dels för att cachen i sig var en rolig utmaning och dels för att det var min allra första Letterbox cache!
Så FTF:ade j.pixbo OUIIUO (Unknown Cache) och hade turen att vara med :).