Calling SecKeychainUnlock with a locked keychain and an invalid password returns errSecSuccess on macOS 26.4

Hi,

In the app I’m working on, we rely on SecKeychainUnlock to verify that a password can be used to unlock the login keychain. When macOS 26.4 rolled out, we started getting bug reports that led me to a discovery that makes me think SecKeychainUnlock behavior was changed. I’m going to illustrate my findings with a sample code:

#include <pwd.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <Security/SecKeychain.h>

#pragma clang diagnostic ignored "-Wdeprecated-declarations"

int
main(void)
{
	char password[100];
	printf("password: ");
	scanf("%s", password);

	struct passwd *home = getpwuid(getuid());
	if (!(home && home->pw_dir))
		return 1;

	char path[1024];
	strcat(path, home->pw_dir);
	strcat(path, "/Library/Keychains/login.keychain-db");

	SecKeychainRef keychain = NULL;
	OSStatus result = SecKeychainOpen(path, &keychain);
	if (result != errSecSuccess) {
		fprintf(stderr, "SecKeychainOpen failed (error %d)\n", result);
		return 1;
	}

	SecKeychainStatus status = 0;
	result = SecKeychainGetStatus(keychain, &status);
	if (result != errSecSuccess) {
		fprintf(stderr, "SecKeychainGetStatus failed (error %d)\n", result);
		return 1;
	}

	if (status & kSecUnlockStateStatus) {
		printf("keychain is unlocked, will try to lock first\n");
		result = SecKeychainLock(keychain);
		if (result != errSecSuccess) {
			fprintf(stderr, "SecKeychainLock failed (error %d)\n", result);
			return 1;
		}
		printf("SecKeychainLock succeeded\n");
	} else {
		printf("keychain is locked\n");
	}

	result = SecKeychainUnlock(keychain, strlen(password), password, TRUE);
	if (result == errSecSuccess) {
		printf("SecKeychainUnlock succeeded\n");
		printf("password '%s' appears to be valid\n", password);
	} else {
		printf("SecKeychainUnlock failed (error %d)\n", result);
		printf("password '%s' appears to be invalid\n", password);
	}

	return 0;
}

Here are the outputs of this program on a machine running macOS 26.3 when provided with a correct password deadbeef and with an incorrect password foobar:

testuser1@tahoe1 kcdebug % ./kcdebug
password: deadbeef
keychain is unlocked, will try to lock first
SecKeychainLock succeeded
SecKeychainUnlock succeeded
password 'deadbeef' appears to be valid

testuser1@tahoe1 kcdebug % ./kcdebug
password: foobar
keychain is unlocked, will try to lock first
SecKeychainLock succeeded
SecKeychainUnlock failed (error -25293)
password 'foobar' appears to be invalid

And here are the outputs of this program on a machine running macOS 26.4:

testuser1@tahoe2 kcdebug % ./kcdebug
password: deadbeef
keychain is unlocked, will try to lock first
SecKeychainLock succeeded
SecKeychainUnlock succeeded
password 'deadbeef' appears to be valid

testuser1@tahoe2 kcdebug % ./kcdebug
password: foobar
keychain is unlocked, will try to lock first
SecKeychainLock succeeded
SecKeychainUnlock succeeded
password 'foobar' appears to be valid

I’m prepared to send a feedback with Feedback Assistant, but I would like to get a confirmation that this is indeed a bug and not an intended change in behavior. I would also like to know what are my options now. SecKeychainUnlock is just a means to an end; what I really need is the ability to keep the keychain password in sync with the user password when the latter is changed by our program.

Thanks in advance.

Answered by DTS Engineer in 883974022
We’re using SecKeychainChangePassword to change the keychain password.

To be clear, that’s not API, and thus not supported by DTS.

There isn’t a good way to change the password for a file-based keychain. I’ve seen folks do it by spawning the security tool with the set-keychain-password command, but that’s problematic on at least two levels:

  • Using command-line tools as API is usually a bad option.
  • Especially in a case like this, where the tool was clearly designed to be used by a user.
  • You either have to pass in the passwords as arguments or do the pseudo-TTY dance, neither of which is ideal.

I discussed your issue with some folks internally and we couldn’t come up with any good suggestions for you. What you really want is an API to change the user’s password in all relevant contexts (OD, login keychain, data protection keychain, FileVault, and probably more than I’m forgetting). Such an API does not exist.

I recommend that you create an enhancement request for that. I can’t promise it’ll ever happen, but you should at least get your request on file.

IMPORTANT Make sure your ER fully captures your requirements. For example:

  • How you’d like to prevent unnecessary work, like you’re currently doing with SecKeychainUnlock.
  • What you expect to happen if the password can be changed in one subsystem but not another.

And please post your bug number, just for the record.

Beyond that, I don’t have any good suggestions for you. You’ve chosen a fundamentally bumpy path, and this but one of many bumps that you’ll encounter )-:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I have a theory about what’s going on here. I’m gonna check on some details and get back to you.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

So, yeah, this is pretty much what I suspected yesterday. The behaviour you’re seeing is fallout from a wider change (r. 154872815). It’s part of a security hardened effort, and thus there’s no easy ‘undo’ option.

what I really need is the ability to keep the keychain password in sync with the user password when the latter is changed by our program.

I’d like to get a better handle on this:

  • Is this part of some sort of identity provider product?
  • From what context are you changing the user’s password? From a GUI app? Or in the background?
  • And what API are you using to do that?
  • And how does confirming the login keychain password help with that process?
  • Presumably that means you also need to change the login keychain password. How are you doing that?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thank you for clarifying that it is intended.

  • It is a part of a passwordless authentication product.
  • We’re changing user’s password from a privileged security agent plugin.
  • We’re using ODRecord’s changePassword(_:toPassword:) to do that.
  • We’re using SecKeychainChangePassword to change the keychain password.
  • Confirming the login keychain via SecKeychainUnlock is an old code which I assume was added to avoid calling SecKeychainChangePassword unnecessarily.
We’re using SecKeychainChangePassword to change the keychain password.

To be clear, that’s not API, and thus not supported by DTS.

There isn’t a good way to change the password for a file-based keychain. I’ve seen folks do it by spawning the security tool with the set-keychain-password command, but that’s problematic on at least two levels:

  • Using command-line tools as API is usually a bad option.
  • Especially in a case like this, where the tool was clearly designed to be used by a user.
  • You either have to pass in the passwords as arguments or do the pseudo-TTY dance, neither of which is ideal.

I discussed your issue with some folks internally and we couldn’t come up with any good suggestions for you. What you really want is an API to change the user’s password in all relevant contexts (OD, login keychain, data protection keychain, FileVault, and probably more than I’m forgetting). Such an API does not exist.

I recommend that you create an enhancement request for that. I can’t promise it’ll ever happen, but you should at least get your request on file.

IMPORTANT Make sure your ER fully captures your requirements. For example:

  • How you’d like to prevent unnecessary work, like you’re currently doing with SecKeychainUnlock.
  • What you expect to happen if the password can be changed in one subsystem but not another.

And please post your bug number, just for the record.

Beyond that, I don’t have any good suggestions for you. You’ve chosen a fundamentally bumpy path, and this but one of many bumps that you’ll encounter )-:

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Calling SecKeychainUnlock with a locked keychain and an invalid password returns errSecSuccess on macOS 26.4
 
 
Q