/* * Copyright 2011 Future Systems * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.krakenapps.radius.protocol; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class UserPasswordAttribute extends RadiusAttribute { private byte[] authenticator; private String sharedSecret; private String password; public UserPasswordAttribute(byte[] authenticator, String sharedSecret, byte[] encoded, int offset, int length) { if (encoded[offset] != getType()) throw new IllegalArgumentException("binary is not user password attribute"); this.authenticator = authenticator; this.sharedSecret = sharedSecret; // copy encoded password int len = encoded[offset + 1] - 2; byte[] b = new byte[len]; for (int i = 0; i < len; i++) b[i] = encoded[offset + 2 + i]; // decode this.password = decodePassword(b); } public UserPasswordAttribute(byte[] authenticator, String sharedSecret, String password) { this.authenticator = authenticator; this.sharedSecret = sharedSecret; this.password = password; } @Override public int getType() { return 2; } public byte[] getAuthenticator() { return authenticator; } public String getSharedSecret() { return sharedSecret; } public String getPassword() { return password; } @Override public byte[] getBytes() { byte[] encoded = encodePassword(); return encodeString(getType(), encoded); } private String decodePassword(byte[] encoded) { // b1 = MD5(S + RA) -> c(1) = p1 xor b1 // b2 = MD5(S + c(1)) -> c(2) = p2 xor b2 try { byte[] plain = new byte[encoded.length]; int folds = encoded.length / 16; byte[] seed = authenticator; for (int i = 0; i < folds; i++) { // b[n] = md5 MessageDigest md5 = MessageDigest.getInstance("MD5"); md5.update(sharedSecret.getBytes()); md5.update(seed); byte[] b = md5.digest(); // plain = c[n] xor b[n] for (int j = 0; j < 16; j++) plain[i * 16 + j] = (byte) (encoded[i * 16 + j] ^ b[j]); // copy result byte[] c = new byte[16]; for (int j = 0; j < 16; j++) c[j] = encoded[j]; seed = c; } return new String(plain).trim(); } catch (NoSuchAlgorithmException e) { // should not reachable return null; } } private byte[] encodePassword() { // b1 = MD5(S + RA) -> c(1) = p1 xor b1 // b2 = MD5(S + c(1)) -> c(2) = p2 xor b2 try { byte[] p = expand(password); byte[] result = new byte[p.length]; int folds = p.length / 16; byte[] seed = authenticator; for (int i = 0; i < folds; i++) { // b[n] = md5 MessageDigest md5 = MessageDigest.getInstance("MD5"); md5.update(sharedSecret.getBytes()); md5.update(seed); byte[] b = md5.digest(); // c[n] = xor byte[] c = new byte[16]; for (int j = 0; j < 16; j++) c[j] = (byte) (p[i * 16 + j] ^ b[j]); // copy result for (int j = 0; j < 16; j++) result[i * 16 + j] = c[j]; seed = c; } return result; } catch (NoSuchAlgorithmException e) { // should not reachable return null; } } private byte[] expand(String password) { if (password == null || password.length() == 0) throw new IllegalArgumentException("password should be not null and not empty"); byte[] p = password.getBytes(); int expandedLength = (((p.length - 1) / 16) + 1) * 16; byte[] expanded = new byte[expandedLength]; for (int i = 0; i < expanded.length; i++) { if (i < p.length) expanded[i] = p[i]; else expanded[i] = 0; } return expanded; } }