Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Question] Can shiro 2.0 + use shiro 1 's hash Algorithm, such as "SHA-256" ? #1022

Closed
1 task done
sam2099 opened this issue Jul 26, 2023 · 19 comments · Fixed by #1263 or #1265
Closed
1 task done

[Question] Can shiro 2.0 + use shiro 1 's hash Algorithm, such as "SHA-256" ? #1022

sam2099 opened this issue Jul 26, 2023 · 19 comments · Fixed by #1263 or #1265
Assignees
Milestone

Comments

@sam2099
Copy link

sam2099 commented Jul 26, 2023

Search before asking

  • I had searched in the issues and found no similar issues.

Question

I use shiro 2.0.0-alpha-2 with shiro 1 's hash Algorithm "SHA-256" , exception as below:

public static String str2SHA256(String str)
    {
        DefaultPasswordService passwordService = new DefaultPasswordService();
        passwordService.setHashService(new DefaultHashService()
        {
            {
                setDefaultAlgorithmName("SHA-256");
            }
        });
        String encryptedValue = passwordService.encryptPassword(str);
        return encryptedValue;
    }

exception:

Exception in thread "main" java.lang.UnsupportedOperationException: Shiro2CryptFormat can only format classes extending AbstractCryptHash.
	at org.apache.shiro.crypto.hash.format.Shiro2CryptFormat.format(Shiro2CryptFormat.java:107)
	at org.apache.shiro.authc.credential.DefaultPasswordService.encryptPassword(DefaultPasswordService.java:83)
@bmarwell
Copy link
Contributor

No, it cannot. We're about to update the documentation about this.

SHA1 is insecure for most applications, which is why only SHA256 upwards (for Hashing) and Argon2 and bcrypt (for storing passwords) are included.

@sam2099
Copy link
Author

sam2099 commented Jul 27, 2023

@bmarwell Thanks.
And how the projects upgrade to shiro 2 from shiro1, the passwords in my project was hasded by SHA256.

@bmarwell
Copy link
Contributor

Oh right! We actually might be able to convert that. Let me fiddle around a bit in the next few days.

However, I highly recommend that you regenerate the passwords using argon2.

@sam2099
Copy link
Author

sam2099 commented Jul 27, 2023

yes, passwords in old project need convert or other processing,
otherwise, old project can not upgrade to shiro 2 .

@lprimak lprimak added this to the 2.0 milestone Jul 29, 2023
@lprimak lprimak added shiro-2.0.0 java Pull requests that update Java code core Core Modules showstopper-for-shiro-2 and removed question labels Aug 28, 2023
@bmarwell
Copy link
Contributor

Hey @sam2099!

Side question: @bdemers I think we talked about an upgrade path two years ago.
IIRC we came to the conclusion, that the best thing an application can do is to ask users to generate new passwords (e.g. reset via email), even if they set the same password again.

While it would not hurt to READ old passwords ONCE, we (Shiro) would not be able if it was still used for storage again. I think this is why support was dropped altogether.

Does that help?

@lprimak
Copy link
Contributor

lprimak commented Oct 16, 2023

that the best thing an application can do is to ask users to generate new passwords (e.g. reset via email), even if they set the same password again.

I would think that would be much easier to do if there was a way to read the password (but not write them) for a good upgrade path.

@bdemers
Copy link
Member

bdemers commented Oct 17, 2023

Depending on the size of (and how much friction it would cause) your user base.
You could reset passwords.

A more complex option would be to check if a user's password has was stored in an older format, if so validate the hash.
If it matches re-hash the password with a different algorithm and store it.

Shiro doesn't provide password write APIs so you would need to write custom code for this.

NOTE: Someone could create a generic password service that would do most of the work UpgradableHashPasswordService, and then delegate the writes to an interface (that would be implemented by developers using Shiro)

@sam2099
Copy link
Author

sam2099 commented Oct 31, 2023

Hey @bmarwell

I would think that would be much easier to do if there was a way to read the password (but not write them) for a good upgrade path.

A more complex option would be to check if a user's password has was stored in an older format, if so validate the hash.
If it matches re-hash the password with a different algorithm and store it.

I would think shiro can be to read the old password (but not write them) , then we guide users to change new password

@bmarwell
Copy link
Contributor

bmarwell commented Nov 5, 2023

I would think shiro can be to read the old password (but not write them) , then we guide users to change new password

No, Shiro cannot read your password. If you used the hash Algorithms from Shiro 1, then the passwords the user enters will be hashed with the same algorithm and THEN compared to the string which was saved.

@lprimak
Copy link
Contributor

lprimak commented Nov 5, 2023

I don’t think he meant “read” literally. But to be able to authenticate using old algorithm and then prompt the user to enter new password and that would be saved using the new algorithm

@sam2099
Copy link
Author

sam2099 commented Nov 6, 2023

I don’t think he meant “read” literally. But to be able to authenticate using old algorithm and then prompt the user to enter new password and that would be saved using the new algorithm

yes

@lprimak lprimak added question and removed java Pull requests that update Java code core Core Modules showstopper-for-shiro-2 labels Dec 23, 2023
@lprimak
Copy link
Contributor

lprimak commented Dec 23, 2023

Hi,
I believe I have a solution to your problem, and it's very simple.

HOWEVER: I highly recommend not to do this, but to encrypt the passwords to the new argon2 format
You just need to add passwordService.setHashFormat(new Shiro1CryptFormat()); to your code

Full example:

public class EncryptTest {
    @Test
    void encryptPassword() {
        DefaultPasswordService passwordService = new DefaultPasswordService();
        passwordService.setHashService(new HS());
        passwordService.setHashFormat(new Shiro1CryptFormat());
        System.out.println(passwordService.encryptPassword("hello123"));
    }

    private final static class HS extends DefaultHashService {
        HS() {
            setDefaultAlgorithmName("SHA-256");
        }
    }
}

I am going to close this issue, if the fix doesn't work, please feel free to re-open it.

@lprimak lprimak closed this as completed Dec 23, 2023
@sam2099
Copy link
Author

sam2099 commented Jan 3, 2024

@lprimak Thanks !

I can not re-open it.

JDK 17/21 + spring boot 3 + shiro 2.0.0-alpha-4

public class EncryptTest {
    @Test
    void encryptPassword() {
        DefaultPasswordService passwordService = new DefaultPasswordService();
        passwordService.setHashService(new HS());
        passwordService.setHashFormat(new Shiro1CryptFormat());
        System.out.println(passwordService.encryptPassword("hello123"));
    }

    private final static class HS extends DefaultHashService {
        HS() {
            setDefaultAlgorithmName("SHA-256");
        }
    }
}

result is

$shiro1$SHA-512$50000$W7dSfsTHQFdLvuBKzq8EfA==$754vzFx0/pcYR4JGL2I24PIw+I8Ba5oqD4pHhEsVezK9MOP41B6ZJH4+mkxHDxTBxVQ2T90RQZ8F1T/7Bl45nw==

I set "SHA-256" but output is "SHA-512" !!!

@lprimak
Copy link
Contributor

lprimak commented Jan 3, 2024

@sam2099 Good catch! I didn't see that one.
I've filed a PR for that issue, but in the meanwhile you can use the code below:

public class EncryptPasswordTest {
    @Test
    void encryptPassword() {
        DefaultPasswordService passwordService = new PasswordService();
        passwordService.setHashService(new HS());
        passwordService.setHashFormat(new Shiro1CryptFormat());
        System.out.println(passwordService.encryptPassword("hello123"));
    }

    private final static class PasswordService extends DefaultPasswordService {
        @Override
        protected HashRequest createHashRequest(ByteSource plaintext) {
            return new HashRequest.Builder().setSource(plaintext)
                    .setAlgorithmName(Sha256Hash.ALGORITHM_NAME)
                    .build();
        }
    }

    private final static class HS extends DefaultHashService {
        HS() {
            setDefaultAlgorithmName(Sha256Hash.ALGORITHM_NAME);
        }
    }
}
@lprimak
Copy link
Contributor

lprimak commented Jan 3, 2024

or even simpler, the whole HS class is no longer necessary

lprimak added a commit that referenced this issue Jan 3, 2024
[#1022] DefaultPasswordService takes HashService default hash…
@sam2099
Copy link
Author

sam2099 commented Jan 4, 2024

@lprimak Thanks very much !

In our old system with shiro1, We set iterations as 1, salt as null; such as password is "123456789o", saved encryptPassword is "$shiro1$SHA-256$1$$jZae727K08KaOmKSgOaGzww/XVqGr/PKEgIMkjrcbJI=". now we need authenticat with shiro 2, code as below:

    @Test
    void encryptPassword() {
        DefaultPasswordService passwordService = new PasswordService();
        passwordService.setHashFormat(new Shiro1CryptFormat());
        System.out.println(passwordService.encryptPassword("123456789o"));

//authenticat   example
        System.out.println(passwordService.passwordsMatch("123456789o", "$shiro1$SHA-256$1$$jZae727K08KaOmKSgOaGzww/XVqGr/PKEgIMkjrcbJI="));
    }

    private final static class PasswordService extends DefaultPasswordService {
        @Override
        protected HashRequest createHashRequest(ByteSource plaintext) {
            return new HashRequest.Builder().setSource(plaintext)
                    .setAlgorithmName(Sha256Hash.ALGORITHM_NAME).addParameter("SimpleHash.iterations", 1)
                    .build();
        }
    }

result Exception

[main] WARN  o.a.shiro.crypto.hash.SimpleHash - Cannot recreate a hash using the same parameters.
java.lang.IllegalArgumentException: Argument for byte conversion cannot be null.
	at org.apache.shiro.lang.codec.CodecSupport.toBytes(CodecSupport.java:203)
	at org.apache.shiro.crypto.hash.SimpleHash.toByteSource(SimpleHash.java:247)
	at org.apache.shiro.crypto.hash.SimpleHash.convertSaltToBytes(SimpleHash.java:233)
	at org.apache.shiro.crypto.hash.SimpleHash.<init>(SimpleHash.java:202)
	at org.apache.shiro.crypto.hash.SimpleHash.matchesPassword(SimpleHash.java:280)
	at org.apache.shiro.authc.credential.DefaultPasswordService.passwordsMatch(DefaultPasswordService.java:108)
	at org.apache.shiro.authc.credential.DefaultPasswordService.passwordsMatch(DefaultPasswordService.java:169)
@lprimak
Copy link
Contributor

lprimak commented Jan 4, 2024

try this and use the result in passwordsMatch() method

        Hash toHash(String encryptedPassword) {
            if (getHashFormatFactory().getInstance(encryptedPassword) instanceof ParsableHashFormat phf) {
                if (phf.parse(encryptedPassword) instanceof SimpleHash sh) {
                    sh.setSalt(ByteSource.Util.bytes(new byte[0]));
                    sh.setIterations(1);
                    return sh;
                }
            }
            throw new IllegalArgumentException("Invalid encrypted password");
        }
lprimak added a commit that referenced this issue Jan 4, 2024
[#1022] bugfix(crypto): Shiro 1 hash parser now supports unsalted passwords
@sam2099
Copy link
Author

sam2099 commented Jan 5, 2024

@lprimak
Thank you !It's done .
code as below

 @Test
    void shiro1PasswordsMatchByShiro2() {
        DefaultPasswordService passwordService = new PasswordService();
        passwordService.setHashFormat(new Shiro1CryptFormat());

        //authenticate   example
        Assertions.assertTrue(passwordService.passwordsMatch("123456", toHash("$shiro1$SHA-256$1$$jZae727K08KaOmKSgOaGzww/XVqGr/PKEgIMkjrcbJI=")));
        Assertions.assertTrue(passwordService.passwordsMatch("123456789o", toHash("$shiro1$SHA-256$1$$qdTQef3V/i7GigwRj0DWlgt2oAMGGbZgjOt4V4lNNoA=")));
        Assertions.assertTrue(passwordService.passwordsMatch("SDGSDG123456789o#@", toHash("$shiro1$SHA-256$1$$6kVafxRNvmDiFQDLEaN09D+UkkLEYfRPSBzNfCKf6Fc=")));
        Assertions.assertTrue(passwordService.passwordsMatch("我是密码123asdfA#@", toHash("$shiro1$SHA-256$1$$+sE4/mtXoDeGiywr0Yt93o1QhhLHgcrshkvV6NjnjaQ=")));
    }
    Hash toHash(String encryptedPassword) {
        if (new DefaultHashFormatFactory().getInstance(encryptedPassword) instanceof ParsableHashFormat phf) {
            if (phf.parse(encryptedPassword) instanceof SimpleHash sh) {
                sh.setSalt(ByteSource.Util.bytes(new byte[0]));
                sh.setIterations(1);
                return sh;
            }
        }
        throw new IllegalArgumentException("Invalid encrypted password");
    }
    private final static class PasswordService extends DefaultPasswordService {
        @Override
        protected HashRequest createHashRequest(ByteSource plaintext) {
            return new HashRequest.Builder().setSource(plaintext)
                    .setAlgorithmName(Sha256Hash.ALGORITHM_NAME)
                    .build();
        }
    }
@lprimak
Copy link
Contributor

lprimak commented Jan 5, 2024

Glad you were successful! You can remove the workarounds when the new version of Shiro is released.

asfgit pushed a commit to apache/ofbiz-framework that referenced this issue Mar 26, 2024
Summary, TL;DR: the changes are minimal and things work like before. OFBiz uses
now Shiro 2.0.0 for AES ciphering instead of Shiro 1.13.0.
OFBiz still uses 3-DES and other (older) ciphering methods in case AES would
fail facing old data.
This also removes now useless
"temporary workaround to compile Shiro 2.0.0 without LDAP"
component block in dependencies.gradle

Details:
This uses
'org.apache.shiro:shiro-crypto-cipher:2.0.0'
instead of previously wrongly committed
org.apache.shiro:shiro-crypto:2.0.0

It re-installs org.apache.shiro:shiro-core:1.13.0
I have still to completely review apache/shiro#1022
According to it, it seems that for now we need to keep shiro-core:1.13.0

http://svn.apache.org/viewvc?view=revision&revision=1814704, and the more
complete dev ML discussion referred in the commit message explains why we keep
3-DES and other (older) ciphering methods. I see no problems with that.
But, we may want to completely get rid of the old 3-DES and old ways by
refactoring this part of code. And maybe offering a way to migrate the data to
AES. The Shiro issue referred above may help in this way.

Thanks: Lenny from Apache Shiro project for the idea.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment