cancel
Showing results for 
Search instead for 
Did you mean: 
cancel
3235
Views
0
Helpful
4
Replies

Convert string to tailf:aes-cfb-128-encrypted-string for authgroup remote-password using MAAPI Java API

Philip Petty
Cisco Employee
Cisco Employee

Hi,

I'm trying to create an authgroup using the MAAPI Java API as previously asked here:

Create an authgroup using MAAPI Java API

In that threat, I got help in creating the umap list but I am still having an issue with creating the encrypted remote-password field.

Below is the code I am currently using:

     ConfPath umapAuthgroupPath = new ConfPath("/ncs:devices/authgroups/group{%s}/umap{admin}", name);

     maapi.create(th, umapAuthgroupPath);

     ConfBuf remoteNameBuf = new ConfBuf(username);

     maapi.setElem(th, remoteNameBuf, umapAuthgroupPath.copyAppend(Ncs._remote_name_));

     ConfBuf remotePasswordBuf = new ConfBuf(password);

     maapi.setElem(th, remotePasswordBuf, umapAuthgroupPath.copyAppend(Ncs._remote_password_));

And below is the exception I am seeing:

     com.tailf.maapi.MaapiException: /ncs:devices/authgroups/group{testing}/umap{admin}/remote-password: <<"testing">> is not a valid value.

Appreciate your assistance with this!

1 Accepted Solution

Accepted Solutions

So I have finally got this working. I stopped being lazy and spent the time to actually read and understand what the MaapiCrypto.decrypt method is doing. Decryption is the reverse of encryption so by understanding the decryption method, we can write an encryption method to get the data in the format that the decrypt method expects and can handle.

The (interesting part of the) decrypt method (and bcopy method that the decrypt method uses) is defined as follows - from MaapiCrypto.java:

private void bcopy(byte[] to, byte[] from, int tostart, int fromstart, int length) {

    for (int i = 0; i < length; i++) {

        to[tostart + i] = from[fromstart + i];

    }

}

public String decrypt(String encryptedString) throws MaapiException {

...

    try {

        // Decode base64 to get bytes

        decodedBytes = Base64.decode(encryptedString.substring(3));

        if (encryptedString.charAt(1) == '8') {

            IV = new byte[16];

            bcopy(IV, decodedBytes, 0, 0, 16);

            encryptedBytesLength = decodedBytes.length - 16;

            encryptedBytes = new byte[encryptedBytesLength];

            bcopy(encryptedBytes, decodedBytes, 0, 16,

            encryptedBytesLength);

        } else {

            // old-style with fixed ivec, "shouldn't happen"

            IV = aesIV;

           encryptedBytes = decodedBytes;

        }

        AesEncrypter aesEnc = new AesEncrypter(aesKey, IV);

        decryptedStr = aesEnc.decrypt(encryptedBytes);

...

So my last post and the NSO documentation, we know that the encrypted string starts with the "$8$" prefix. The line below is stripping out this prefix and decoding the string from Base64 encoding - therefore we know that the last thing two things we need to do when creating our encrypted strings are to encode it in Base64 and then add the "$8$" prefix.

decodedBytes = Base64.decode(encryptedString.substring(3));


We then fall into the first if case because our prefix string has '8' at index = 1. This section of the code is separating the initial 16 bytes into an IV (Initialization Vector) byte array from the remaining bytes, the encryptedBytes, by using the bcopy method. So now we know our encrypted data is a byte array of the initialization vector used to encrypt the data (16 bytes long) followed by the encrypted data itself. We also know that the encryption algorithm is AES/CFB128 from my last post and the NSO documentation (and looking at the AesEncrypter implementation), so we have enough information to write our own encrypt method, as follows:


private static String encrypt(Maapi maapi, String str) throws Exception {

    MaapiCrypto crypto = new MaapiCrypto(maapi);

    byte[] IV;

    byte[] encryptedBytes;

    byte[] encodedBytes;

    IV = new byte[16];

    new Random().nextBytes(IV);

    IvParameterSpec ivSpec = new IvParameterSpec(IV);

    SecretKeySpec skeySpec = new SecretKeySpec(crypto.getAesKey(), "AES");

    Cipher cipher = Cipher.getInstance("AES/CFB/NoPadding");

    cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivSpec);

    encryptedBytes = cipher.doFinal(str.getBytes());

    encodedBytes = new byte[encryptedBytes.length + 16];

    bcopy(encodedBytes, IV, 0, 0, 16);

    bcopy(encodedBytes, encryptedBytes, 16, 0, encryptedBytes.length);

    return "$8$" + Base64.encodeBytes(encodedBytes);

}


This method takes a Maapi instance and the string to encrypt as input arguments. Using the input Maapi instance, we create a CryptoMaapi instance, this is to get the AES keys to encrypt the input string with shortly. We then create and initialize an initialization vector (IV) 16 bytes long with random values. This IV and the AES keys from the CryptoMaapi instance are used to initialize a cipher using the AES/CFB128 algorithm. This cipher is then used to encrypt our input string into the encryptedBytes byte array. The encodedBytes byte array is then initialized to be the length of the encryptedBytes plus the IV byte arrays. The next two calls to bcopy pack the encodedBytes byte array with first the IV and then the encryptedBytes. All we need to do then is return this encryptedBytes byte array encoded as Base64 with the expected "$8$" prefix and we're done.

To test this you can use the CryptoMaapi.decrypt() method and verify that it returns your initial input string.


I am unsure why the CryptoMaapi class doesn't contain an encrypt method, IMO it really should and it would look very similar to the above. Would appreciate if someone from NSO could comment on this.


As a side note, in my post above, I was missing the Base64.encodeBytes() function calls. The old encryption method I was using (with "$4$" prefix) work but this isn't accepted by the Maapi API when sent in the ConfBuf; only strings with the "$8$" prefix/IV packed byte array encryption method appear to be accepted.

View solution in original post

4 Replies 4

davidmb
Level 1
Level 1

Have you tried the MaapiCrypto.encrypt method?  For example:

ConfBuf remotePasswordBuf = new ConfBuf(new MaapiCrypto(maapi).encrypt(password));

Hi David,

Thank you for taking a look at this.

According to the JAR's that came with NSO 4.4, the MaapiCrypto class doesn't have an encrypt method; only decrypt.

I have spent a bit more time on this and I found some references in the NSO documentation that encrypted strings require a prefix to indicate that they are in fact already encrypted. From tailf_yang_extensions:

aes-cfb-128-encrypted-string

The aes-cfb-128-encrypted-string works exactly like des3-cbc-encrypted-string but AES/128bits in CFB mode is used to encrypt the string. The prefix for encrypted values is '$8$'.

I am now doing the below and the Maapi API seems to like this:

     MaapiCrypto crypto = new MaapiCrypto(maapi);

     AesEncrypter aesEnc = new AesEncrypter(crypto.getAesKey(), crypto.getAesIV());

     byte[] encPasswordBytes = aesEnc.encrypt(password);

     String encryptedPassword = encPasswordBytes.toString();

     ConfBuf remotePasswordBuf = new ConfBuf("$8$" + encryptedPassword);

However, if I try to decode the encrypted password string with the MaapiCrypto instance I used to create it, I get the following exception:

Code:

System.out.println("Encrypted password: " + encryptedPassword);

System.out.println("Decrypted password: " + aesEnc.decrypt(encPasswordBytes));

System.out.println("MaapiCrypto decrypted password: " + crypto.decrypt("$8$" + encryptedPassword));

Output:

Encrypted password: [B@6b4a4e18

Decrypted password: testing

Bad Base64 input character at 0: 91(decimal)

java.lang.NullPointerException

    at com.tailf.maapi.MaapiCrypto.bcopy(MaapiCrypto.java:137)

    at com.tailf.maapi.MaapiCrypto.decrypt(MaapiCrypto.java:207)

    at AddDevice.authgroupCreate(AddDevice.java:161)

    at AddDevice.main(AddDevice.java:90)

I think what I am doing above using the crypto.getAesIV() method may be an old method which would correspond to the "$4$" prefix, however changing this prefix resulted in a MaapiException. Reading the MaapiCrypto implementation, I believe I may need to do something different with the IV (initial vector) when using the "$8$" prefix but I'm not clear what that is. I have looked through the source/manuals but I can't find an example for this.

So I have finally got this working. I stopped being lazy and spent the time to actually read and understand what the MaapiCrypto.decrypt method is doing. Decryption is the reverse of encryption so by understanding the decryption method, we can write an encryption method to get the data in the format that the decrypt method expects and can handle.

The (interesting part of the) decrypt method (and bcopy method that the decrypt method uses) is defined as follows - from MaapiCrypto.java:

private void bcopy(byte[] to, byte[] from, int tostart, int fromstart, int length) {

    for (int i = 0; i < length; i++) {

        to[tostart + i] = from[fromstart + i];

    }

}

public String decrypt(String encryptedString) throws MaapiException {

...

    try {

        // Decode base64 to get bytes

        decodedBytes = Base64.decode(encryptedString.substring(3));

        if (encryptedString.charAt(1) == '8') {

            IV = new byte[16];

            bcopy(IV, decodedBytes, 0, 0, 16);

            encryptedBytesLength = decodedBytes.length - 16;

            encryptedBytes = new byte[encryptedBytesLength];

            bcopy(encryptedBytes, decodedBytes, 0, 16,

            encryptedBytesLength);

        } else {

            // old-style with fixed ivec, "shouldn't happen"

            IV = aesIV;

           encryptedBytes = decodedBytes;

        }

        AesEncrypter aesEnc = new AesEncrypter(aesKey, IV);

        decryptedStr = aesEnc.decrypt(encryptedBytes);

...

So my last post and the NSO documentation, we know that the encrypted string starts with the "$8$" prefix. The line below is stripping out this prefix and decoding the string from Base64 encoding - therefore we know that the last thing two things we need to do when creating our encrypted strings are to encode it in Base64 and then add the "$8$" prefix.

decodedBytes = Base64.decode(encryptedString.substring(3));


We then fall into the first if case because our prefix string has '8' at index = 1. This section of the code is separating the initial 16 bytes into an IV (Initialization Vector) byte array from the remaining bytes, the encryptedBytes, by using the bcopy method. So now we know our encrypted data is a byte array of the initialization vector used to encrypt the data (16 bytes long) followed by the encrypted data itself. We also know that the encryption algorithm is AES/CFB128 from my last post and the NSO documentation (and looking at the AesEncrypter implementation), so we have enough information to write our own encrypt method, as follows:


private static String encrypt(Maapi maapi, String str) throws Exception {

    MaapiCrypto crypto = new MaapiCrypto(maapi);

    byte[] IV;

    byte[] encryptedBytes;

    byte[] encodedBytes;

    IV = new byte[16];

    new Random().nextBytes(IV);

    IvParameterSpec ivSpec = new IvParameterSpec(IV);

    SecretKeySpec skeySpec = new SecretKeySpec(crypto.getAesKey(), "AES");

    Cipher cipher = Cipher.getInstance("AES/CFB/NoPadding");

    cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivSpec);

    encryptedBytes = cipher.doFinal(str.getBytes());

    encodedBytes = new byte[encryptedBytes.length + 16];

    bcopy(encodedBytes, IV, 0, 0, 16);

    bcopy(encodedBytes, encryptedBytes, 16, 0, encryptedBytes.length);

    return "$8$" + Base64.encodeBytes(encodedBytes);

}


This method takes a Maapi instance and the string to encrypt as input arguments. Using the input Maapi instance, we create a CryptoMaapi instance, this is to get the AES keys to encrypt the input string with shortly. We then create and initialize an initialization vector (IV) 16 bytes long with random values. This IV and the AES keys from the CryptoMaapi instance are used to initialize a cipher using the AES/CFB128 algorithm. This cipher is then used to encrypt our input string into the encryptedBytes byte array. The encodedBytes byte array is then initialized to be the length of the encryptedBytes plus the IV byte arrays. The next two calls to bcopy pack the encodedBytes byte array with first the IV and then the encryptedBytes. All we need to do then is return this encryptedBytes byte array encoded as Base64 with the expected "$8$" prefix and we're done.

To test this you can use the CryptoMaapi.decrypt() method and verify that it returns your initial input string.


I am unsure why the CryptoMaapi class doesn't contain an encrypt method, IMO it really should and it would look very similar to the above. Would appreciate if someone from NSO could comment on this.


As a side note, in my post above, I was missing the Base64.encodeBytes() function calls. The old encryption method I was using (with "$4$" prefix) work but this isn't accepted by the Maapi API when sent in the ConfBuf; only strings with the "$8$" prefix/IV packed byte array encryption method appear to be accepted.

Getting Started

Find answers to your questions by entering keywords or phrases in the Search bar above. New here? Use these resources to familiarize yourself with the NSO Developer community: