Les,
Thanks, that was tons of help! I'm seeing Java class names decrypted now,
so I think decryption is working. Here's the javascript code to run in
node.js:
var decrypt = function (input, password, callback) {
// Decode input from base64 to binary string
var decoded = new Buffer(input, 'base64').toString('binary');
// The first 128 bits (16 bytes) are the prepended IV
var iv = decoded.slice(0,16);
// The remaining is the actual encrypted data
var data = decoded.slice(16);
// Decode password from Base64
var key = new Buffer(password, 'base64').toString('binary');
// Decipher encrypted data
var decipher = crypto.createDecipheriv('aes-128-cfb', key, iv);
decipher.setAutoPadding(false);
var decrypted = decipher.update(data, 'binary') +
decipher.final('binary');
var plaintext = new Buffer(decrypted, 'binary').toString('ascii');
// Return decrypted text
callback(plaintext);
};
var key = 'UaNG2oxPGFh1kC4QS0/1Rw==';
// Real rememberMe for 'testuser' using key on development environment
using DefaultSerializer
var rememberMe =
'jVIfq39P7KmDZDEI5vY7+wlhe0dA2J7wd5Sak9AIeVXfuyB6KtGNMIg3LLNLELhQ7BjaO+k5XjoHX7CepC3+YeP9/s4F+dZcfs69UMLZEo1rk1knf/bXK3/90q2ksVQuIVFVtKy0OYU22f1eQX01SHw6btK2sZ+WBmFNDYzJAcX2kSTgENgIqSrRqH/W9ora1NaOlxKy5+VKs7qU1AocLUmoO5AKqg3EaXs99PjykzadD8Wc/kCIz5tBmpQbxjC/By7f7Aqs7U2nxxkzXo68TTDLtZu4u4XhcVvk7+goCWYZT35zN3pWkoOLiMsy4pH5DVRnaOEdCE4NGUKOnomcrvEdChkZoNE+Q7FYPBtz1mEf5EXsNOOl5iAa2etVbmN9VtWDlfsvOCKq2KHBcR+EWYILOEFAGjEUKWS6pz0ISKl8ftqX8LC3E/m3t4aAJMRIWXdf+K6EQ8EYbAfVjRC0xnDRzmAHxdF5RHj27vQI14znuvOg8oynoj/5TliOq3xxpzUdgnCbxQBEquqDC4IO8Wpaftm7NZt/b/KP6+jo7NNXqoliicgsc0iaHc7PVkny9Xrp34Da5lUS3UFaJLGHAQ==';
decrypt(rememberMe, key, function(output) {
console.log(output);
});
console.log('-------------------------------------');
// XMLSerializer based rememberMe
var xmlRememberMe =
'X1UyjjAxJVxryO9kAVjj9WZ3Y4gPvz1daS8MryTC3rXcw7EhFare4/7kG1Y4ojkLK7hV8tjn/z5M9T0YmFaqBJjjhCPEn0ty860vI0HDCrhY++rR+L21Q1SVkJ5CRPEiD4PP5tgy2dTiGcrv1VvI0Lwm7uFA72bHp796xqWbW/NdKYicdWDkFH0zVwOxXF6dfCyCjME1nd2UzOl76hmJDVpkJqkkj9GmtAetbB6Muk+ySKkeK+Dc7KrVCBXnS0B0yB5MIoDFqjpq1e28BDADEQ==';
decrypt(xmlRememberMe, key, function(output) {
console.log(output);
});
However, it looks like the payload is a byte array of Java serialization
data. This isn't terribly useful, as you can see:
��sr2org.apache.shiro.subject.SimplePrincipalCollection�X%JLrealmPrincipalstLjava/util/Map;xpsrjava.util.LinkedHashMap4�N\l��Z
accessOrderxrjava.util.HashMap���`�F
loadFactorI thresholdxp?@
t
SprtzRealmsrjava.util.LinkedHashSet�l�Z��*xrjava.util.HashSet�D�����4xpw
[email protected];���̏#�Jvaluexrjava.lang.Number���
���xp*xxwq~x
So I've been messing around configuring my RememberMeManager to use the
XMLSerializer instead. I configure it in Spring like this:
<bean id="xmlSerializer" class="org.apache.shiro.io.XmlSerializer">
</bean>
<bean name="sportZingRememberMeManager"
class="com.sprtz.security.SportZingRememberMeManager">
<property name="cipherKey" ref="cipherKeyBytes"/>
<property name="cookie.domain" value="${global.cookieDomain}"/>
<property name="cookie.path" value="/"/>
<property name="serializer" ref="xmlSerializer"/>
</bean>
It looks like it is using it, because if I put a breakpoint in
getRememberedPrinciplals, this.serializer is an instance of XMLSerializer:
public class SportZingRememberMeManager extends CookieRememberMeManager {
private static final Logger log =
LoggerFactory.getLogger(SportZingRememberMeManager.class);
private MemberService memberService;
@Autowired
public void setMemberService(MemberService memberService) {
this.memberService = memberService;
}
@Override
public PrincipalCollection getRememberedPrincipals(SubjectContext
subjectContext) {
PrincipalCollection principals =
super.getRememberedPrincipals(subjectContext);
if ( principals != null ) {
Long id = (Long) principals.getPrimaryPrincipal();
memberService.updateAccessed(id);
}
return principals;
}
}
However, if I decrypt the rememberMe cookie that was generated this way, it
still contains garbage and classnames and doesn't look like XML. Plus,
calling super.getRememberedPrincipals() always returns an empty object, so
rememberme features aren't working.
Basically, all I need is some way to extract the user name or user ID from
the cookie. The output above should contain the ID number 1834 somewhere,
but I really don't want to write a Java object deserializer in Javascript!
(Although, if I did, this might be useful:
http://docs.oracle.com/javase/6/docs/platform/serialization/spec/protocol.html
)
Any suggestions? I don't think writing a custom Serializer that only saves
the User ID would work, since Shiro is expecting to deserialize the entire
PrincipalsCollection data. Any idea why the XMLSerializer wouldn't decrypt
to XML and the serialization wouldn't work?
Thanks,
Tauren
On Mon, Jan 14, 2013 at 2:15 PM, Les Hazlewood <[email protected]>wrote:
> P.S. Shiro defaults all String encoding to UTF-8.
>
> On Mon, Jan 14, 2013 at 2:02 PM, Les Hazlewood <[email protected]>
> wrote:
> > Hi Tauren,
> >
> > Shiro 1.0 and later uses an AesCipherService to perform
> > encryption/decryption of the cookie. The cookie value amounts to
> > essentially the following (pseudo code - if you were to collapse the
> > relevant operations into a single method):
> >
> > byte[] key = getEncryptionKeyBytes();
> > byte[] iv = generateSecureRandomInitializationVector(128);
> > byte[] serializedPrincipalCollection =
> jdkSerialize(subject.getPrincipals());
> > byte[] encrypted = encrypt(serializedPrincipalCollection, key, iv);
> > //I know you can't concat byte arrays in java, but this is psuedocode!:
> > byte[] combined = initializationVector + encrypted;
> > String cookieValue = Base64.encode(combined);
> >
> > When the cookie is read from a request, the process is reversed:
> >
> > byte[] combined = Base64.decrypt(cookieValue);
> > //The first 128 bits (16 bytes) are the prepended IV:
> > byte[] iv = combined[0]..combined[15];
> > //The remaining is the actual encrypted data:
> > byte[] encrypted = combined[16]..combined[combined.length-1];
> > byte[] serializedPrincipals = decrypt(encrypted, key, iv);
> > PrincipalCollection principals = jdkDeserialize(serializedPrincipals);
> >
> > In Shiro 1.1.0, the AesCipherService's default settings for byte array
> > encryption were:
> >
> > Key size: 128 (bits)
> > Block size: 128 (bits)
> > Mode of operation: CFB
> > Padding scheme: PKCS5
> > Initialization Vector size: 128 (bits)
> > Autogenerate and prefix IVs: true
> >
> > In Shiro 1.2.0+, the default settings are the same except for the Mode
> > of Operation. In Shiro 1.2+, the mode of operation is CBC. (This
> > change was mentioned in the 1.2.0 release notes:
> > http://svn.apache.org/repos/asf/shiro/trunk/RELEASE-NOTES).
> >
> > HTH!
> >
> > --
> > Les Hazlewood | @lhazlewood
> > CTO, Stormpath | http://stormpath.com | @goStormpath | 888.391.5282
> > Stormpath wins GigaOM Structure Launchpad Award! http://bit.ly/MvZkMk
> >
> > On Sun, Jan 13, 2013 at 4:38 PM, Tauren Mills <[email protected]>
> wrote:
> >> I'm trying to figuring out how to decrypt a Shiro rememberMe cookie
> using
> >> javascript running in a node.js/express.js server app. If anyone has
> been
> >> down this path or has any advice of any sort, please let me know!
> >>
> >> I'd like to extract identity principals from the rememberMe cookie once
> I'm
> >> able to decrypt it. I assume this is available, but what exactly is
> stored
> >> in the rememberMe cookie?
> >>
> >> How is the rememberMe cookie encrypted? Does it use an AES cipher and
> then
> >> Base64 encode it? Is padding used, what type? What about string
> encoding
> >> ('utf8', ascii', etc)? There seem to be lots of combinations and
> figuring
> >> out the right one is daunting.
> >>
> >> I'm currently using Shiro 1.1.0. I've been looking at the following
> >> resources but don't haven't solved it yet:
> >>
> >>
> http://stackoverflow.com/questions/10548973/encrypting-and-decrypting-with-python-and-nodejs
> >>
> http://stackoverflow.com/questions/12685475/encrypt-in-java-decrypt-in-node-js
> >>
> http://stackoverflow.com/questions/11477175/encrypting-and-decrypting-data-through-transport-through-java-to-node-js
> >>
> >> Here's some config info if it helps:
> >>
> >> <bean id="securityManager"
> >> class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
> >> <property name="cacheManager" ref="cacheManager"/>
> >> <property name="realm" ref="myRealm"/>
> >> <property name="sessionMode" value="native"/>
> >> <property name="rememberMeManager" ref="myRememberMeManager"/>
> >> </bean>
> >>
> >> <bean name="myRememberMeManager"
> >> class="com.myapp.security.MyRememberMeManager">
> >> <property name="cipherKey" ref="cipherKeyBytes"/>
> >> <property name="cookie.domain" value="${global.cookieDomain}"/>
> >> <property name="cookie.path" value="/"/>
> >> </bean>
> >> <bean id="cipherKeyBytes"
> >>
> class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
> >> <property name="targetClass" value="org.apache.shiro.codec.Base64"/>
> >> <property name="targetMethod" value="decode"/>
> >> <property name="arguments">
> >> <list>
> >> <value>mySecretValue</value>
> >> </list>
> >> </property>
> >> </bean>
> >>
> >> Thanks!
> >> Tauren
> >>
>