Tuesday, September 29, 2009

 

XML Encryption with Apache XML Security

這幾天在弄這個東西搞得很煩不過還是弄好了
所以在這裡記錄一下以免之後忘掉還是怎樣
很奇怪他library裡面對於encryption與decryption附的sample code
是用secret key(symmetric key)加密內容後再用另一把secret key加密那把secret key
然後傳來傳去的就是那把secret key
我不是很了解其中的原因
所以把它改成我知道比較ok的方式就是
使用public key加密那把加密內容的secret key
傳過去後再用自己的private key解開

但這小小的變更卻是麻煩的開始
之前總是出現一些奇怪的Exception像是
org.apache.xml.security.encryption.XMLEncryptionException: decryptElement called without a key and unable to resolve
或是
javax.crypto.BadPaddingException: Given final block not properly padded
不過弄了幾天後
在我下面這個例子裡面是沒有出現啦~


首先是我拿來測試用的XML檔案

swanky.xml

<?xml version="1.0" encoding="UTF-8"?>

<Test>
  <Name>Swanky Hsiao</Name>
  <Blog>http://swanbear.blogspot.com/</Blog>
  <Flickr>http://www.flickr.com/photos/swanky-hsiao/</Flickr>
  <Plurk>http://www.plurk.com/swanky</Plurk>
  <Facebook>http://www.facebook.com/swanky</Facebook>
  <FacebookFansGroup>http://www.facebook.com/pages/0e801f0a/172447000328</FacebookFansGroup>
</Test>

然後用keytool產生keystore
輸入後除了一開始要你指定key的密碼是打password之外都可以亂打

swanky.keystore

keytool -keystore swanky.keystore -genkey -alias swanky-key -keyalg RSA -keypass password

XML檔案跟keystore準備好後就可以測試了~
我這裡是都寫在一個class裡面方便使用!
除了可以直接用encryElement()指定要加密的Element外
也可以像我這例子裡面放多個xpath進行多個Element的加密

ApacheXMLSecurityTest.java

import java.io.CharArrayWriter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.xml.security.encryption.EncryptedKey;
import org.apache.xml.security.encryption.XMLCipher;
import org.apache.xml.security.keys.KeyInfo;
import org.apache.xml.security.utils.EncryptionConstants;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.sun.org.apache.xml.internal.serialize.OutputFormat;
import com.sun.org.apache.xml.internal.serialize.XMLSerializer;

public class ApacheXMLSecurityTest {
   static {
      org.apache.xml.security.Init.init();
   }

   static private String keystoreName = "swanky.keystore";
   static private String keystorePassword = "password";
   static private String keyAlias = "swanky-key";

   public static void main(String[] args) {
      Document doc = parseXML("swanky.xml");

      String[] xpaths = new String[] { "Test/Plurk", "Test/Facebook" };
      encryptXPaths(doc, xpaths);
      System.out.println("Encrypt:\n" + serialDoc(doc));

      decryptDoc(doc);
      System.out.println("Decrypt:\n" + serialDoc(doc));
   }

   public static Document parseXML(String uri) {
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      Document doc = null;

      try {
         DocumentBuilder docBuilder = factory.newDocumentBuilder();
         doc = docBuilder.parse(uri);
      } catch (Exception e) {
         e.printStackTrace();
      }

      return doc;
   }

   public static Writer serialDoc(Document doc) {
      CharArrayWriter writer = new CharArrayWriter();

      OutputFormat format = new OutputFormat(doc);
      format.setLineWidth(65);
      format.setIndenting(true);
      format.setIndent(2);
      XMLSerializer serializer = new XMLSerializer(writer, format);
      try {
         serializer.serialize(doc);
      } catch (IOException e) {
         e.printStackTrace();
      }
      return writer;
   }

   public static void encryptXPaths(Document doc, String[] xpaths) {
      try {
         for (int i = 0; i < xpaths.length; i++) {
            String xpath = xpaths[i];
            NodeList list = evaluateXPath(doc, xpath);
            for (int j = 0; j < list.getLength(); j++) {
               Node node = (Node) list.item(j);
               Element element = (Element) node;
               encryElement(doc, element);
            }
         }
      } catch (XPathExpressionException e) {
         e.printStackTrace();
      }
   }

   private static NodeList evaluateXPath(Document doc, String expression)
         throws XPathExpressionException {
      NodeList nodes;
      XPathFactory factory = XPathFactory.newInstance();
      XPath xpath = factory.newXPath();

      XPathExpression expr = xpath.compile(expression);

      Object result = expr.evaluate(doc, XPathConstants.NODESET);
      nodes = (NodeList) result;

      return nodes;
   }

   public static void encryElement(Document doc, Element element) {
      try {
         SecretKey symmetricKey = generateDataEncryptionKey();

         XMLCipher xmlCipher = XMLCipher.getInstance(XMLCipher.AES_128);
         xmlCipher.init(XMLCipher.ENCRYPT_MODE, symmetricKey);
         PublicKey pubKey = getKeyStore(keystoreName, keystorePassword)
               .getCertificate("swanky-key").getPublicKey();
         XMLCipher keyCipher = XMLCipher.getInstance(XMLCipher.RSA_v1dot5);
         keyCipher.init(XMLCipher.WRAP_MODE, pubKey);
         EncryptedKey encryptedKey = keyCipher.encryptKey(doc, symmetricKey);

         KeyInfo keyInfo = new KeyInfo(doc);
         keyInfo.add(encryptedKey);
         xmlCipher.getEncryptedData().setKeyInfo(keyInfo);

         xmlCipher.doFinal(doc, element, true);
      } catch (Exception e) {
         e.printStackTrace();
      }
   }

   static KeyStore getKeyStore(String keyStoreFileName, String password) {
      KeyStore store = null;
      try {
         store = KeyStore.getInstance("JKS", "SUN");
         InputStream in = new FileInputStream(keyStoreFileName);
         store.load(in, password.toCharArray());
         in.close();
      } catch (Exception e) {
         e.printStackTrace();
      }
      return store;
   }

   private static SecretKey kek;

   private static SecretKey generateDataEncryptionKey() {
      if (kek == null) {
         try {
            String jceAlgorithmName = "AES";
            KeyGenerator keyGenerator = KeyGenerator
                  .getInstance(jceAlgorithmName);
            keyGenerator.init(128);
            kek = keyGenerator.generateKey();
         } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
         }
      }
      return kek;
   }

   public static Document decryptDoc(Document doc) {
      try {
         XMLCipher xmlCipher = XMLCipher.getInstance();
         xmlCipher.init(XMLCipher.DECRYPT_MODE, null);
         PrivateKey privateKey = (PrivateKey) getKeyStore(keystoreName,
               keystorePassword).getKey(keyAlias,
               keystorePassword.toCharArray());
         xmlCipher.setKEK(privateKey);

         NodeList list = doc.getElementsByTagNameNS(
               EncryptionConstants.EncryptionSpecNS,
               EncryptionConstants._TAG_ENCRYPTEDDATA);

         while (list.getLength() > 0) {
            Node node = (Node) list.item(0);
            Element encryptedDataElement = (Element) node;
            xmlCipher.doFinal(doc, encryptedDataElement);
         }
      } catch (Exception e) {
         e.printStackTrace();
      }
      return doc;
   }
}

執行結果是像這樣的


Encrypt:
<?xml version="1.0" encoding="UTF-8"?>
<Test>
  <Name>Swanky Hsiao</Name>
  <Blog>http://swanbear.blogspot.com/</Blog>
  <Flickr>http://www.flickr.com/photos/swanky-hsiao/</Flickr>
  <Plurk>
    <xenc:EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Content" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
      <xenc:EncryptionMethod
        Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"/>
      <ds:KeyInfo>
        <xenc:EncryptedKey xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
          <xenc:EncryptionMethod
            Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"/>
          <xenc:CipherData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
            <xenc:CipherValue xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">n0VFmIo5y+Ra+0Qnd0zQIbNK2N8QuLz6rQAd2UgHKjfLnhjAzFy9lX+wpcLdk31F5zvxw+G4yFKt
VFZaQn7/bW1tOUoHmoepdIZ9ZsQHWS7NZRsaKUjaLjIj777Rt5paCQUGOhI6yf7QyPh+zGkUwoPI
lbIyZeEXO9w6MjetnI0=</xenc:CipherValue>
          </xenc:CipherData>
        </xenc:EncryptedKey>
      </ds:KeyInfo>
      <xenc:CipherData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
        <xenc:CipherValue xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">Vo/J6LhCT8wA8ohdgo0g2CgmDnFznQ/hz3GkpMxJZu5ELTA6Paj+FVR9hgEicDSg</xenc:CipherValue>
      </xenc:CipherData>
    </xenc:EncryptedData>
  </Plurk>
  <Facebook>
    <xenc:EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Content" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
      <xenc:EncryptionMethod
        Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"/>
      <ds:KeyInfo>
        <xenc:EncryptedKey xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
          <xenc:EncryptionMethod
            Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"/>
          <xenc:CipherData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
            <xenc:CipherValue xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">OY5WBZW/aFssBJ/ZaHkSo+UncYCQF/fdnOnLsXrZBZVtKKvfVZkVEMzKNYmtdquqZKcOd5FYfet7
3SW7P6GNEhuTGPmA3L/+tlKroONg50ATe6HBb5CbkPhArfDJmtEu8zvQCxOkXUACO3KCYc2RQ7/g
Ri3BH+q3M9YLpDT/GVE=</xenc:CipherValue>
          </xenc:CipherData>
        </xenc:EncryptedKey>
      </ds:KeyInfo>
      <xenc:CipherData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
        <xenc:CipherValue xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">8UufIfFlEUXHhQ+l1owFOq1m/MDEW+4Psn/kCqY5Y2RDLWRZu7smOPfveYUAJmJA</xenc:CipherValue>
      </xenc:CipherData>
    </xenc:EncryptedData>
  </Facebook>
  <FacebookFansGroup>http://www.facebook.com/pages/0e801f0a/172447000328</FacebookFansGroup>
</Test>

Decrypt:
<?xml version="1.0" encoding="UTF-8"?>
<Test>
  <Name>Swanky Hsiao</Name>
  <Blog>http://swanbear.blogspot.com/</Blog>
  <Flickr>http://www.flickr.com/photos/swanky-hsiao/</Flickr>
  <Plurk>http://www.plurk.com/swanky</Plurk>
  <Facebook>http://www.facebook.com/swanky</Facebook>
  <FacebookFansGroup>http://www.facebook.com/pages/0e801f0a/172447000328</FacebookFansGroup>
</Test>

Labels: , , , , ,


Thursday, August 14, 2008

 

SignatureTool (byte to char to byte)

之前要把簽章的值放到文字檔案內
簽章後得到的值是byte[]
放到文字檔案則需要String,或是char[]
但不知道為什麼
總是會出現 java.security.SignatureException: invalid encoding for signature
我試過許多byte轉成char的方式
包括自己位元運算硬寫出來的
用了不下四、五種API
看了一堆encoding的東西之後還是會有問題
實在是相當納悶
不過最後終於找到下面這種用BigInteger來轉的方式才OK


package swanky.util;

import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;

public class SignatureTool {
  public static byte[] getSignature(String message, PrivateKey privKey) {
    byte[] signature = null;
    try {
      // initial signature object
      Signature signalg = Signature.getInstance("DSA");
      signalg.initSign(privKey);

      // sign message
      signalg.update(message.getBytes());
      signature = signalg.sign();

    } catch (Exception e) {
      e.printStackTrace();
    }
    return signature;
  }

  public static boolean verify(String message, PublicKey pubKey,
      byte[] signature) {
    boolean result = true;
    try {
      // initial signature object
      Signature verifyalg = Signature.getInstance("DSA");
      verifyalg.initVerify(pubKey);

      // update signature object
      verifyalg.update(message.getBytes());

      if (!verifyalg.verify(signature))
        result = false;
    } catch (java.security.SignatureException se) {
      se.printStackTrace();
    } catch (Exception e) {
      e.printStackTrace();
    }
    return result;
  }

  private static final int KEYSIZE = 512;

  public static void main(String[] args) throws Exception {
    String message = "test";

    KeyPairGenerator pairgen = KeyPairGenerator.getInstance("DSA");
    SecureRandom random = new SecureRandom();
    pairgen.initialize(KEYSIZE, random);
    KeyPair keyPair = pairgen.generateKeyPair();

    PublicKey pubKey = keyPair.getPublic();
    PrivateKey privKey = keyPair.getPrivate();

    byte[] signature = getSignature(message, privKey);
    System.out.println("raw signature: " + signature.toString());

    String encodeSig = toHexString(signature);
    System.out.println("string signature: " + encodeSig);

    byte[] decodeSig = fromHexString(encodeSig);
    System.out.println("signature: " + decodeSig);

    boolean result = verify(message, pubKey, decodeSig);
    System.out.println("verify result=" + result);

    result = verify(message + "fake", pubKey, decodeSig);
    System.out.println("verify result=" + result);
  }

  public static String toHexString(byte[] in) {
    BigInteger temp = new BigInteger(in);
    return temp.toString(16);
  }

  public static byte[] fromHexString(String in) {
    BigInteger temp = new BigInteger(in, 16);
    return temp.toByteArray();
  }
}

執行結果

raw signature: [B@1bf73fa
string signature: 302d0215008a1cf6cb2a0228fb8a4330f308c19789e1992bb5021444db35e4c9f40b5efce53e9ea3c3821b3d3d4058
signature: [B@5740bb
verify result=true
verify result=false

但是呢你看
原本簽章的值跟轉回來的簽章值print出來的東西不一樣耶...
這到底是怎麼回事? @@"

Labels: , , ,


Monday, March 10, 2008

 

JAAS Callback Example

JAAS已經有內建讓你從console或是跳出一個GUI視窗讓使用者填入帳號密碼進行Authentication的功能了
如果你想要有其他種方式
就必須去實作CallbackHandler這個interface
下面我提供一個只有兩個類別、一個設定檔的範例給大家參考

SampleLoginModule.java


import java.util.Map;

import java.util.Arrays;

import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;

public class SampleLoginModule implements LoginModule {

  private Subject subject;
  private CallbackHandler callbackHandler;
  private Map<String, ?> sharedState;
  private Map<String, ?> options;

  public void initialize(Subject subject, CallbackHandler callbackHandler,
      Map<String, ?> sharedState, Map<String, ?> options) {
    this.subject = subject;
    this.callbackHandler = callbackHandler;
    this.sharedState = sharedState;
    this.options = options;
  }

  public boolean login() throws LoginException {
    if (callbackHandler == null)
      throw new LoginException("Error: no CallbackHandler");

    Callback[] callbacks = new Callback[2];
    callbacks[0] = new NameCallback("user name: ");
    callbacks[1] = new PasswordCallback("password: ", true);

    try {
      callbackHandler.handle(callbacks);
    } catch (Exception e) {
      e.printStackTrace();
    }
    String username = ((NameCallback) callbacks[0]).getName();
    char[] password = ((PasswordCallback) callbacks[1]).getPassword();

    System.out.println("Username: " + username);
    System.out.println("Password: " + Arrays.toString(password));

    return true;
  }

  public boolean commit() throws LoginException {
    return true;
  }

  public boolean abort() throws LoginException {
    return true;
  }

  public boolean logout() throws LoginException {
    return true;
  }
}

Test.java


import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginContext;

import com.sun.security.auth.callback.DialogCallbackHandler;
import com.sun.security.auth.callback.TextCallbackHandler;

public class Test {

  public static void main(String[] args) throws Exception {
    CallbackHandler textCallbackHandler = new TextCallbackHandler();
    CallbackHandler dialogCllbackHandler = new DialogCallbackHandler();

//    LoginContext lc = new LoginContext("Sample", textCallbackHandler);
    LoginContext lc = new LoginContext("Sample", dialogCllbackHandler);
    lc.login();
  }
}

sample_jaas.config


Sample {
    SampleLoginModule required;
};

放在同一個目錄中
執行


javac *.java
java -Djava.security.auth.login.config=sample_jaas.config Test
就可以跑跑看啦!

延伸閱讀

  1. jini寫的JAAS authentication 的介紹

Labels: , , , ,


 

Simple Swing Login Form

下面這段code會產生出上面那樣的小Dialog視窗讓你輸入帳號密碼進行登入
是最近玩JAAS時寫的
不過其實在JAAS裡面有更方便讓人拿來用的東西
那就是javax.security.auth.callback package與com.sun.security.auth.callback package裡面的東東
怎麼用的話...有機會再跟大家說


JPanel panel = new JPanel(new GridLayout(2, 2));
JTextField nameField = new JTextField();
JPasswordField passwordField = new JPasswordField();
panel.add(new JLabel("ID"));
panel.add(nameField);
panel.add(new JLabel("Password"));
panel.add(passwordField);
while (JOptionPane.showConfirmDialog(null, panel, "Please Login",
JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE) != 0) {
}
String username = nameField.getText();
char[] password = passwordField.getPassword();

Labels: , ,