diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SybilRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/SybilRenderer.java
index 98671a146fe49055105c8f47db120377aa24d577..fb45e0cb27680cd61a5d46529a4b08e298b4dd04 100644
--- a/apps/routerconsole/java/src/net/i2p/router/web/SybilRenderer.java
+++ b/apps/routerconsole/java/src/net/i2p/router/web/SybilRenderer.java
@@ -4,6 +4,7 @@ import java.io.IOException;
 import java.io.Serializable;
 import java.io.Writer;
 import java.math.BigInteger;
+import java.text.Collator;
 import java.text.DecimalFormat;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -24,6 +25,7 @@ import net.i2p.data.router.RouterInfo;
 import net.i2p.data.router.RouterKeyGenerator;
 import net.i2p.router.RouterContext;
 import net.i2p.router.TunnelPoolSettings;
+import net.i2p.router.crypto.FamilyKeyCrypto;
 import net.i2p.router.peermanager.DBHistory;
 import net.i2p.router.peermanager.PeerProfile;
 import net.i2p.router.tunnel.pool.TunnelPool;
@@ -61,6 +63,8 @@ class SybilRenderer {
     private static final double POINTS_US24 = 25.0;
     private static final double POINTS_US16 = 10.0;
     private static final double POINTS_FAMILY = -2.0;
+    private static final double POINTS_BAD_OUR_FAMILY = 100.0;
+    private static final double POINTS_OUR_FAMILY = -100.0;
     private static final double MIN_CLOSE = 242.0;
     private static final double PAIR_DISTANCE_FACTOR = 2.0;
     private static final double OUR_KEY_FACTOR = 4.0;
@@ -370,6 +374,7 @@ class SybilRenderer {
 
     private static class FoofComparator implements Comparator<String>, Serializable {
          private final ObjectCounter<String> _o;
+         private final Collator _comp = Collator.getInstance();
          public FoofComparator(ObjectCounter<String> o) { _o = o;}
          public int compare(String l, String r) {
              // reverse by count
@@ -377,7 +382,7 @@ class SybilRenderer {
              if (rv != 0)
                  return rv;
              // foward by name
-             return l.compareTo(r);
+             return _comp.compare(l, r);
         }
     }
 
@@ -578,6 +583,8 @@ class SybilRenderer {
         }
         List<String> foo = new ArrayList<String>(oc.objects());
         Collections.sort(foo, new FoofComparator(oc));
+        FamilyKeyCrypto fkc = _context.router().getFamilyKeyCrypto();
+        String ourFamily = fkc != null ? fkc.getOurFamilyName() : null;
         boolean found = false;
         for (String s : foo) {
             int count = oc.count(s);
@@ -593,10 +600,16 @@ class SybilRenderer {
                 // limit display
                 //renderRouterInfo(buf, info, null, false, false);
                 double point = POINTS_FAMILY;
-                if (count > 1)
+                if (fkc != null && s.equals(ourFamily)) {
+                    if (fkc.verifyOurFamily(info))
+                        addPoints(points, info.getHash(), POINTS_OUR_FAMILY, "Our family \"" + DataHelper.escapeHTML(s) + "\" with " + (count - 1) + " other" + (( count > 2) ? "s" : ""));
+                    else
+                        addPoints(points, info.getHash(), POINTS_BAD_OUR_FAMILY, "Spoofed our family \"" + DataHelper.escapeHTML(s) + "\" with " + (count - 1) + " other" + (( count > 2) ? "s" : ""));
+                } else if (count > 1) {
                     addPoints(points, info.getHash(), point, "Same declared family \"" + DataHelper.escapeHTML(s) + "\" with " + (count - 1) + " other" + (( count > 2) ? "s" : ""));
-                else
+                } else {
                     addPoints(points, info.getHash(), point, "Declared family \"" + DataHelper.escapeHTML(s) + '"');
+                }
             }
         }
         if (!found)
diff --git a/router/java/src/net/i2p/router/crypto/FamilyKeyCrypto.java b/router/java/src/net/i2p/router/crypto/FamilyKeyCrypto.java
index cdb0811dc7b52a7d83cfaeaf1a84e1ea81e29ea4..f1bfe2063a316196cb20003015fee061ebde2ce9 100644
--- a/router/java/src/net/i2p/router/crypto/FamilyKeyCrypto.java
+++ b/router/java/src/net/i2p/router/crypto/FamilyKeyCrypto.java
@@ -10,6 +10,7 @@ import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.cert.X509Certificate;
 import java.security.cert.X509CRL;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
@@ -44,6 +45,7 @@ public class FamilyKeyCrypto {
     private final Log _log;
     private final Map<Hash, String> _verified;
     private final Set<Hash> _negativeCache;
+    private final Set<Hash> _ourFamily;
     // following for verification only, otherwise null
     private final String _fname;
     private final SigningPrivateKey _privkey;
@@ -82,13 +84,15 @@ public class FamilyKeyCrypto {
         _fname = _context.getProperty(PROP_FAMILY_NAME);
         if (_fname != null) {
             if (_fname.contains("/") || _fname.contains("\\") ||
-                _fname.contains("..") || (new File(_fname)).isAbsolute())
-                throw new GeneralSecurityException("Illegal family name");
+                _fname.contains("..") || (new File(_fname)).isAbsolute() ||
+                _fname.length() <= 0)
+                throw new GeneralSecurityException("Illegal family name: " + _fname);
         }
         _privkey = (_fname != null) ? initialize() : null;
         _pubkey = (_privkey != null) ? _privkey.toPublic() : null;
         _verified = new ConcurrentHashMap<Hash, String>(4);
         _negativeCache = new ConcurrentHashSet<Hash>(4);
+        _ourFamily = (_privkey != null) ? new ConcurrentHashSet<Hash>(4) : Collections.<Hash>emptySet();
     }
     
     /** 
@@ -144,6 +148,35 @@ public class FamilyKeyCrypto {
         return rv;
     }
 
+    /** 
+     *  Do we have a valid family?
+     *  @since 0.9.28
+     */
+    public boolean hasFamily() {
+        return _pubkey != null;
+    }
+
+    /** 
+     *  Get verified members of our family.
+     *  Will not contain ourselves.
+     *
+     *  @return non-null, not a copy, do not modify
+     *  @since 0.9.28
+     */
+    public Set<Hash> getOurFamily() {
+        return _ourFamily;
+    }
+
+    /** 
+     *  Get our family name.
+     *
+     *  @return name or null
+     *  @since 0.9.28
+     */
+    public String getOurFamilyName() {
+        return _fname;
+    }
+
     /** 
      *  Verify the family signature in a RouterInfo.
      *  @return true if good sig or if no family specified at all
@@ -152,6 +185,44 @@ public class FamilyKeyCrypto {
         String name = ri.getOption(OPT_NAME);
         if (name == null)
             return true;
+        return verify(ri, name);
+    }
+
+    /** 
+     *  Verify the family in a RouterInfo matches ours and the signature is good.
+     *  Returns false if we don't have a family and sig, or they don't.
+     *  Returns false for ourselves.
+     *
+     *  @return true if family matches with good sig
+     *  @since 0.9.28
+     */
+    public boolean verifyOurFamily(RouterInfo ri) {
+        if (_pubkey == null)
+            return false;
+        String name = ri.getOption(OPT_NAME);
+        if (!_fname.equals(name))
+            return false;
+        Hash h = ri.getHash();
+        if (_ourFamily.contains(h))
+            return true;
+        if (h.equals(_context.routerHash()))
+            return false;
+        boolean rv = verify(ri, name);
+        if (rv) {
+            _ourFamily.add(h);
+            _log.logAlways(Log.INFO, "Found and verified member of our family (" + _fname + "): " + h);
+        } else {
+            if (_log.shouldWarn())
+                _log.warn("Found spoofed member of our family (" + _fname + "): " + h);
+        }
+        return rv;
+    }
+
+    /** 
+     *  Verify the family in a RouterInfo, name already retrieved
+     *  @since 0.9.28
+     */
+    private boolean verify(RouterInfo ri, String name) {
         Hash h = ri.getHash();
         String ssig = ri.getOption(OPT_SIG);
         if (ssig == null) {