{"record":{"id":"665f68cc35168e3c","repo":"puppetlabs/puppet","slug":"os-x-versions-10-7-require-a-salted-sha512-pbkdf","errorCode":null,"errorMessage":"OS X versions > 10.7 require a Salted SHA512 PBKDF2 password hash of 256 characters. Please check your password and try again.","messagePattern":"OS X versions > 10\\.7 require a Salted SHA512 PBKDF2 password hash of 256 characters\\. Please check your password and try again\\.","errorType":"exception","errorClass":"Puppet::Error","httpStatus":null,"severity":"error","filePath":"lib/puppet/provider/user/directoryservice.rb","lineNumber":361,"sourceCode":"\n  # If you thought GETTING a password was bad, try SETTING it. This method\n  # makes me want to cry. A thousand tears...\n  #\n  # I've been unsuccessful in tracking down a way to set the password for\n  # a user using dscl that DOESN'T require passing it as plaintext. We were\n  # also unable to get dsimport to work like this. Due to these downfalls,\n  # the sanest method requires opening the user's plist, dropping in the\n  # password hash, and serializing it back to disk. The problems with THIS\n  # method revolve around dscl. Any time you directly modify a user's plist,\n  # you need to flush the cache that dscl maintains.\n  def password=(value)\n    if self.class.get_os_version == '10.7'\n      if value.length != 136\n        raise Puppet::Error, \"OS X 10.7 requires a Salted SHA512 hash password of 136 characters.  Please check your password and try again.\"\n      end\n    else\n      if value.length != 256\n        raise Puppet::Error, \"OS X versions > 10.7 require a Salted SHA512 PBKDF2 password hash of 256 characters. Please check your password and try again.\"\n      end\n\n      assert_full_pbkdf2_password\n    end\n\n    # Methods around setting the password on OS X are the ONLY methods that\n    # cannot use dscl (because the only way to set it via dscl is by passing\n    # a plaintext password - which is bad). Because of this, we have to change\n    # the user's plist directly. DSCL has its own caching mechanism, which\n    # means that every time we call dscl in this provider we're not directly\n    # changing values on disk (instead, those calls are cached and written\n    # to disk according to Apple's prioritization algorithms). When Puppet\n    # needs to set the password property on OS X > 10.6, the provider has to\n    # tell dscl to write its cache to disk before modifying the user's\n    # plist. The 'dscacheutil -flushcache' command does this. Another issue\n    # is how fast Puppet makes calls to dscl and how long it takes dscl to\n    # enter those calls into its cache. We have to sleep for 2 seconds before\n    # flushing the dscl cache to allow all dscl calls to get INTO the cache","sourceCodeStart":343,"sourceCodeEnd":379,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/provider/user/directoryservice.rb#L343-L379","documentation":"Raised by Puppet's macOS DirectoryService user provider on OS X newer than 10.7 when the `password` property value is not exactly 256 characters — the combined length of a Salted SHA-512 PBKDF2 hash (salt + entropy hex + iterations segments as the provider concatenates them). After the length check, assert_full_pbkdf2_password additionally verifies all three components are present.","triggerScenarios":"Managing `password` on macOS >= 10.8 with a 136-char 10.7-style salted SHA-512 hash, a plaintext string, or a PBKDF2 value missing a component so the concatenated form is the wrong length.","commonSituations":"Old manifests from 10.7 carried forward after an OS upgrade; hashes generated with wrong output length (e.g., truncated entropy); omitting the iterations segment.","solutions":["Generate the full PBKDF2 form: iterations + 64-hex-char salt + 128-hex-char entropy, concatenated to exactly 256 characters.","Extract a working example from an existing user: `dscl . -read /Users/<name> ShadowHashData` and hex-decode to confirm the layout.","Version your password data in Hiera by OS version so 10.7 and 10.8+ hosts get the right format.","Ensure assert_full_pbkdf2_password passes by including all three parts, not just the digest."],"exampleFix":"# before (macOS 10.8+)\nuser { 'alice': ensure => present, password => 'abcdefgh<128 hex chars>' }  # 136 chars, 10.7 format\n# after - 256 chars: iterations(3) + salt(64 hex) + entropy(128 hex) ... total 256\nuser { 'alice': ensure => present, password => lookup('users::alice::pbkdf2_hash', Sensitive) }","handlingStrategy":"type-guard","validationCode":"raise 'expected 256 chars' unless hash.length == 256\nparts = hash.partition_valid_pbkdf2  # salt(64) + entropy(128) + iterations present","typeGuard":"def valid_pbkdf2_256?(hash)\n  hash.is_a?(String) && hash.length == 256 && hash.match?(/\\A[0-9a-fA-F]+\\z/)\nend","tryCatchPattern":null,"preventionTips":["Verify hash length (256) and segment presence before assigning to `password`.","After OS upgrades, regenerate hashes in the newer PBKDF2 format.","Extract a known-good example from a working user's ShadowHashData as a template."],"tags":["macos","user-management","password-hash","pbkdf2","puppet"],"backgroundTag":"password-hash-format-invalid","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}